Top Banner
1087

TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

May 22, 2020

Download

Documents

dariahiddleston
Welcome message from author
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Page 1: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management
Page 2: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScript:ModernJavaScriptDevelopment

Page 3: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TableofContents

TypeScript:ModernJavaScriptDevelopmentTypeScript:ModernJavaScriptDevelopmentCreditsPreface

WhatthislearningpathcoversWhatyouneedforthislearningpathWhothislearningpathisforReaderfeedbackCustomersupportDownloadingtheexamplecodeErrataPiracyQuestions

1.Module11.IntroducingTypeScript

TheTypeScriptarchitectureDesigngoalsTypeScriptcomponents

TypeScriptlanguagefeaturesTypes

OptionalstatictypenotationVariables,basictypes,andoperators

Var,let,andconstUniontypesTypeguardsTypealiasesAmbientdeclarationsArithmeticoperatorsComparisonoperatorsLogicaloperatorsBitwiseoperatorsAssignmentoperators

FlowcontrolstatementsThesingle-selectionstructure(if)Thedouble-selectionstructure(if…else)Theinlineternaryoperator(?)Themultiple-selectionstructure(switch)Theexpressionistestedatthetopoftheloop(while)Theexpressionistestedatthebottomoftheloop(do…while)Iterateoneachobject'sproperties(for…in)Countercontrolledrepetition(for)

Page 4: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionsClassesInterfacesNamespaces

PuttingeverythingtogetherSummary

2.AutomatingYourDevelopmentWorkflowAmoderndevelopmentworkflowPrerequisites

Node.jsAtomGitandGitHub

SourcecontroltoolsPackagemanagementtools

npmBowertsd

TaskrunnersCheckingthequalityoftheTypeScriptcodeCompilingtheTypeScriptcodeOptimizingaTypeScriptapplicationManagingtheGulptasks'executionorder

TestrunnersSynchronizedcross-devicetestingContinuousIntegrationtoolsScaffoldingtoolsSummary

3.WorkingwithFunctionsWorkingwithfunctionsinTypeScript

FunctiondeclarationsandfunctionexpressionsFunctiontypesFunctionswithoptionalparametersFunctionswithdefaultparametersFunctionswithrestparametersFunctionoverloadingSpecializedoverloadingsignaturesFunctionscopeImmediatelyinvokedfunctionsGenericsTagfunctionsandtaggedtemplates

AsynchronousprogramminginTypeScriptCallbacksandhigher-orderfunctionsArrowfunctionsCallbackhell

Page 5: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PromisesGeneratorsAsynchronousfunctions–asyncandawait

Summary4.Object-OrientedProgrammingwithTypeScript

SOLIDprinciplesClassesInterfacesAssociation,aggregation,andcomposition

AssociationAggregationComposition

InheritanceMixins

GenericclassesGenericconstraints

MultipletypesingenerictypeconstraintsThenewoperatoringenerictypes

ApplyingtheSOLIDprinciplesTheLiskovsubstitutionprincipleTheinterfacesegregationprincipleThedependencyinversionprinciple

NamespacesModules

ES6modules–runtimeanddesigntimeExternalmodules–designtimeonlyAMDmodules–runtimeonlyCommonJSmodules–runtimeonlyUMDmodules–runtimeonlySystemJSmodules–runtimeonly

CirculardependenciesSummary

5.RuntimeTheenvironmentTheruntime

FramesStackQueueHeapTheeventloop

ThethisoperatorThethisoperatorintheglobalcontextThethisoperatorinafunctioncontextThecall,apply,andbindmethods

Page 6: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrototypesInstancepropertiesversusclasspropertiesPrototypalinheritanceTheprototypechainAccessingtheprototypeofanobjectThenewoperator

ClosuresStaticvariableswithclosuresPrivatememberswithclosures

Summary6.ApplicationPerformance

PrerequisitesPerformanceandresourcesPerformancemetrics

AvailabilityTheresponsetimeProcessingspeedLatencyBandwidthScalability

PerformanceanalysisNetworkperformanceanalysisNetworkperformanceanduserexperience

NetworkperformancebestpracticesandrulesGPUperformanceanalysis

Framespersecond(FPS)CPUperformanceanalysisMemoryperformanceanalysisThegarbagecollector

PerformanceautomationPerformanceoptimizationautomationPerformancemonitoringautomationPerformancetestingautomation

ExceptionhandlingTheErrorclassThetry…catchstatementsandthrowstatements

Summary7.ApplicationTesting

SoftwaretestingglossaryAssertionsSpecsTestcasesSuitesSpies

Page 7: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DummiesStubsMocksTestcoverage

PrerequisitesGulpKarmaIstanbulMochaChaiSinon.JSTypedefinitionsPhantomJSSeleniumandNightwatch.js

TestingplanningandmethodologiesTest-drivendevelopmentBehavior-drivendevelopment(BDD)Testsplansandtesttypes

SettingupatestinfrastructureBuildingtheapplicationwithGulpRunningtheunittestwithKarmaRunningE2EtestswithSeleniumandNightwatch.js

Creatingtestassertions,specs,andsuiteswithMochaandChaiTestingtheasynchronouscodeAssertingexceptionsTDDversusBDDwithMochaandChai

TestspiesandstubswithSinon.JSSpiesStubs

Creatingend-to-endtestswithNightwatch.jsGeneratingtestcoveragereportsSummary

8.DecoratorsPrerequisitesAnnotationsanddecorators

TheclassdecoratorsThemethoddecoratorsThepropertydecoratorsTheparameterdecoratorsThedecoratorfactoryDecoratorswithargumentsThereflectionmetadataAPI

Summary9.ApplicationArchitecture

Page 8: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thesingle-pageapplicationarchitectureTheMV*architectureCommoncomponentsandfeaturesintheMV*frameworks

ModelsCollectionsItemviewsCollectionviewsControllersEventsRouterandhash(#)navigationMediatorDispatcherClient-siderenderingandVirtualDOMUserinterfacedatabinding

One-waydatabindingTwo-waydatabinding

DataflowWebcomponentsandshadowDOM

ChoosinganapplicationframeworkWritinganMVCframeworkfromscratch

PrerequisitesApplicationeventsMediatorApplicationRouteEventemitterRouterDispatcherControllerModelandmodelsettingsViewandviewsettingsFramework

Summary10.PuttingEverythingTogether

PrerequisitesTheapplication'srequirementsTheapplication'sdataTheapplication'sarchitectureTheapplication'sfilestructureConfiguringtheautomatedbuildTheapplication'slayoutImplementingtherootcomponentImplementingthemarketcontrollerImplementingtheNASDAQmodel

Page 9: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingtheNYSEmodelImplementingthemarketviewImplementingthemarkettemplateImplementingthesymbolcontroller

ImplementingthequotemodelImplementingthesymbolviewImplementingthechartmodelImplementingthechartviewTestingtheapplicationPreparingtheapplicationforaproductionreleaseSummary

2.Module21.ToolsandFrameworks

InstallingtheprerequisitesInstallingNode.jsInstallingTypeScriptcompiler

ChoosingahandyeditorVisualStudioCode

ConfiguringVisualStudioCodeOpeningafolderasaworkspaceConfiguringaminimumbuildtask

SublimeTextwithTypeScriptpluginInstallingPackageControlInstallingtheTypeScriptplugin

OthereditororIDEoptionsAtomwiththeTypeScriptpluginVisualStudioWebStorm

GettingyourhandsontheworkflowConfiguringaTypeScriptproject

Introductiontotsconfig.jsonCompileroptions

targetmoduledeclarationsourceMapjsxnoEmitOnErrornoEmitHelpersnoImplicitAnyexperimentalDecorators*emitDecoratorMetadata*outDiroutFile

Page 10: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

rootDirpreserveConstEnumsstrictNullChecksstripInternal*isolatedModules

AddingsourcemapsupportDownloadingdeclarationsusingtypings

InstallingtypingsDownloadingdeclarationfilesOption"save"

TestingwithMochaandIstanbulMochaandChai

WritingtestsinJavaScriptWritingtestsinTypeScript

GettingcoverageinformationwithIstanbulTestinginrealbrowserswithKarma

CreatingabrowserprojectInstallingKarmaConfiguringandstartingKarma

IntegratingcommandswithnpmWhynototherfancybuildtools?

Summary2.TheChallengeofIncreasingComplexity

ImplementingthebasicsCreatingthecodebaseDefiningtheinitialstructureofthedatatobesynchronizedGettingdatabycomparingtimestampsTwo-waysynchronizingThingsthatwentwrongwhileimplementingthebasics

PassingadatastorefromtheservertotheclientdoesnotmakesenseMakingtherelationshipsclear

GrowingfeaturesSynchronizingmultipleitems

SimplyreplacingdatatypewithanarrayServer-centeredsynchronization

SynchronizingfromtheservertotheclientSynchronizingfromclienttoserver

SynchronizingmultipletypesofdataSupportingmultipleclientswithincrementaldata

UpdatingtheclientsideUpdatingserverside

SupportingmoreconflictmergingNewdatastructuresUpdatingclientside

Page 11: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UpdatingtheserversideThingsthatgowrongwhileimplementingeverything

PilingupsimilaryetparallelprocessesDatastoresthataretremendouslysimplified

GettingthingsrightFindingabstractionImplementingstrategiesWrappingstores

Summary3.CreationalDesignPatterns

FactorymethodParticipantsPatternscopeImplementationConsequences

AbstractFactoryParticipantsPatternscopeImplementationConsequences

BuilderParticipantsPatternscopeImplementationConsequences

PrototypeSingleton

BasicimplementationsConditionalsingletons

Summary4.StructuralDesignPatterns

CompositePatternParticipantsPatternscopeImplementationConsequences

DecoratorPatternParticipantsPatternscopeImplementation

ClassicaldecoratorsDecoratorswithES-nextsyntax

ConsequencesAdapterPattern

Page 12: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsPatternscopeImplementationConsequences

BridgePatternParticipantsPatternscopeImplementationConsequences

FaçadePatternParticipantsPatternscopeImplementationConsequences

FlyweightPatternParticipantsPatternscopeImplementationConsequences

ProxyPatternParticipantsPatternscopeImplementationConsequences

Summary5.BehavioralDesignPatterns

ChainofResponsibilityPatternParticipantsPatternscopeImplementationConsequences

CommandPatternParticipantsPatternscopeImplementationConsequences

MementoPatternParticipantsPatternscopeImplementationConsequences

IteratorPatternParticipantsPatternscope

Page 13: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationSimplearrayiteratorES6iterator

ConsequencesMediatorPattern

ParticipantsPatternscopeImplementationConsequences

Summary6.BehavioralDesignPatterns:Continuous

StrategyPatternParticipantsPatternscopeImplementationConsequences

StatePatternParticipantsPatternscopeImplementationConsequences

TemplateMethodPatternParticipantsPatternscopeImplementationConsequences

ObserverPatternParticipantsPatternscopeImplementationConsequences

VisitorPatternParticipantsPatternscopeImplementationConsequences

Summary7.PatternsandArchitecturesinJavaScriptandTypeScript

Promise-basedwebarchitecturePromisifyingexistingmodulesorlibrariesViewsandcontrollersinExpressAbstractionofresponsesAbstractionofpermissionsExpectederrors

Page 14: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DefiningandthrowingexpectederrorsTransformingerrors

ModularizingprojectAsynchronouspatterns

WritingpredictablecodeAsynchronouscreationalpatternsAsynchronousmiddlewareandhooksEvent-basedstreamparser

Summary8.SOLIDPrinciples

SingleresponsibilityprincipleExampleChoosinganaxis

Open-closedprincipleExampleAbstractioninJavaScriptandTypeScriptRefactorearlier

LiskovsubstitutionprincipleExampleTheconstraintsofsubstitution

InterfacesegregationprincipleExamplePropergranularity

DependencyinversionprincipleExampleSeparatinglayers

Summary9.TheRoadtoEnterpriseApplication

CreatinganapplicationDecisionbetweenSPAand"normal"webapplicationsTakingteamcollaborationintoconsideration

BuildingandtestingprojectsStaticassetspackagingwithwebpack

IntroductiontowebpackBundlingJavaScriptLoadingTypeScriptSplittingcodeLoadingotherstaticassets

AddingTSLinttoprojectsIntegratingwebpackandtslintcommandwithnpmscripts

VersioncontrolGitflow

MainbranchesSupportingbranches

Page 15: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FeaturebranchesReleasebranchesHotfixbranches

SummaryofGitflowPullrequestbasedcodereview

ConfiguringbranchpermissionsCommentsandmodificationsbeforemerge

TestingbeforecommitsGithooksAddingpre-commithookautomatically

ContinuousintegrationConnectingGitHubrepositorywithTravis-CI

DeploymentautomationPassivedeploymentbasedonGitserversidehooksProactivedeploymentbasedontimersornotifications

Summary3.Module3

1.TypeScript2.0FundamentalsWhatisTypeScript?Quickexample

TranspilingTypechecking

LearningmodernJavaScriptletandconstClassesArrowfunctionsFunctionargumentsArrayspreadDestructuringTemplatestringsNewclasses

TypecheckingPrimitivetypesDefiningtypesUndefinedandnullTypeannotations

Summary2.AWeatherForecastWidgetwithAngular2

UsingmodulesSettinguptheproject

DirectorystructureConfiguringTypeScriptBuildingthesystemTheHTMLfile

Page 16: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthefirstcomponentThetemplateTestingInteractionsOne-wayvariablebindingEventlisteners

AddingconditionstothetemplateDirectivesThetemplatetagModifyingtheabouttemplate

UsingthecomponentinothercomponentsShowingaforecast

UsingtheAPITypingtheAPI

CreatingtheforecastcomponentTemplatesDownloadingtheforecastAdding@Output

ThemaincomponentUsingourothercomponentsTwo-waybindingsListeningtooureventGeolocationAPIComponentsources

Summary3.Note-TakingAppwithaServer

SettinguptheprojectstructureDirectoriesConfiguringthebuildtoolTypedefinitions

GettingstartedwithNodeJSAsynchronouscode

CallbackapproachforasynchronouscodeDisadvantagesofcallbacks

ThedatabaseWrappingfunctionsinpromisesConnectingtothedatabaseQueryingthedatabase

UnderstandingthestructuraltypesystemGenericsTypingtheAPI

AddingauthenticationImplementingusersinthedatabaseAddinguserstothedatabase

Page 17: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheAPIAddingCRUDoperations

ImplementingthehandlersRequesthandling

WritingtheclientsideCreatingtheloginformCreatingamenuThenoteeditorThemaincomponent

ErrorhandlerRunningtheapplicationSummary

4.Real-TimeChatSettinguptheproject

ConfiguringgulpGettingstartedwithReact

CreatingacomponentwithJSXAddingpropsandstatetoacomponentCreatingthemenuTestingtheapplication

WritingtheserverConnectionsTypingtheAPIAcceptingconnectionsStoringrecentmessagesHandlingasessionImplementingachatmessagesession

ConnectingtotheserverAutomaticreconnectingSendingamessagetotheserverWritingtheeventhandler

CreatingthechatroomTwo-waybindingsStatelessfunctionalcomponentsRunningtheapplication

ComparingReactandAngularTemplatesandJSXLibrariesorframeworks

Summary5.NativeQRScannerApp

GettingstartedwithNativeScriptCreatingtheprojectstructure

AddingTypeScriptCreatingaHelloWorldpage

Page 18: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthemainviewAddingadetailsviewScanningQRcodes

TypedefinitionsImplementationTestingonadevice

AddingpersistentstorageStylingtheappComparingNativeScripttoalternativesSummary

6.AdvancedProgramminginTypeScriptUsingtypeguards

NarrowingNarrowinganyCombiningtypeguards

MoreaccuratetypeguardsAssignments

CheckingnullandundefinedGuardagainstnullandundefinedThenevertype

CreatingtaggeduniontypesComparingperformanceofalgorithms

Big-OhnotationOptimizingalgorithmsBinarysearchBuilt-infunctions

Summary7.SpreadsheetApplicationswithFunctionalProgramming

SettinguptheprojectFunctionalprogramming

CalculatingafactorialUsingdatatypesforexpressions

CreatingdatatypesTraversingdatatypesValidatinganexpressionCalculatingexpressions

ParsinganexpressionCreatingcoreparsersRunningparsersinasequenceParsinganumberOrderofoperations

DefiningthesheetCalculatingallfields

UsingtheFluxarchitecture

Page 19: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DefiningthestateCreatingthestoreanddispatcher

CreatingactionsAddingacolumnorarowChangingthetitleShowingtheinputpopupTestingactions

WritingtheviewRenderingthegridRenderingafieldShowingthepopupAddingstylesGluingeverythingtogether

AdvantagesofFluxGoingcross-platform

Summary8.PacManinHTML5

SettinguptheprojectUsingtheHTML5canvas

SavingandrestoringthestateDesigningtheframework

CreatingpicturesWrappingotherpicturesCreatingeventsBindingeverythingtogether

DrawingonthecanvasAddingutilityfunctionsCreatingthemodels

UsingenumsStoringthelevelCreatingthedefaultlevelCreatingthestate

DrawingtheviewHandlingevents

WorkingwithkeycodesCreatingthetimehandlerRunningthegameAddingamenu

ChangingthemodelRenderingthemenuHandlingeventsModifyingthetimehandler

Summary9.PlayingTic-Tac-ToeagainstanAI

Page 20: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtheprojectstructureConfigureTypeScript

AddingutilityfunctionsCreatingthemodels

ShowingthegridCreatingoperationsonthegridCreatingthegridAddingtestsRandomtesting

ImplementingtheAIusingMinimaxImplementingMinimaxinTypeScriptOptimizingthealgorithm

CreatingtheinterfaceHandlinginteractionCreatingplayers

TestingtheAITestingwitharandomplayer

Summary10.MigrateJavaScripttoTypeScript

GraduallymigratingtoTypeScriptAddingTypeScript

ConfiguringTypeScriptConfiguringthebuildtoolAcquiringtypedefinitionsTestingtheproject

MigratingeachfileConvertingtoESmodulesCorrectingtypesAddingtypeguardsandcastsUsingmodernsyntaxAddingtypes

RefactoringtheprojectEnablestrictchecks

SummaryA.BibliographyIndex

Page 21: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScript:ModernJavaScriptDevelopment

Page 22: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScript:ModernJavaScriptDevelopmentLeveragethefeaturesofTypeScripttoboostyourdevelopmentskillsandcreatecaptivatingwebapplications

Acourseinthreemodules

BIRMINGHAM-MUMBAI

Page 23: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScript:ModernJavaScriptDevelopmentCopyright©2016PacktPublishing

Allrightsreserved.Nopartofthiscoursemaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthiscoursetoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthiscourseissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthors,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythiscourse.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthiscoursebytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Publishedon:December2016

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

BirminghamB32PB,UK.

ISBN978-1-78728-908-6

www.packtpub.com

Page 24: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreditsAuthors

RemoH.Jansen

VilicVane

IvoGabedeWolff

Reviewers

LiviuIgnat

JakubJedryszek

AndrewLeithMacrae

BrandonMills

IvoGabedeWolff

WanderWang

MatthewHill

ContentDevelopmentEditor

RohitKumarSingh

Graphics

JasonMonteiro

ProductionCoordinator

ShraddhaFalebhai

Page 25: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrefaceItwasn’talongtimeagothatmanyJavaScriptengineersor,mostofthetime,webfrontendengineers,werestillfocusingonsolvingdetailedtechnicalissues,suchashowtolayoutspecificcontentcross-browsersandhowtosendrequestscross-domains.

Atthattime,agoodwebfrontendengineerwasusuallyexpectedtohavenotableexperienceonhowdetailedfeaturescanbeimplementedwithexistingAPIs.Onlyafewpeoplecaredabouthowtowriteapplication-scaleJavaScriptbecausetheinteractiononawebpagewasreallysimpleandnoonewroteASPinJavaScript.

However,thesituationhaschangedtremendously.JavaScripthasbecometheonlylanguagethatrunseverywhere,cross-platformandcross-device.Inthemainbattlefield,interactionsontheWebbecomemoreandmorecomplex,andpeoplearemovingbusinesslogicfromthebackendtothefrontend.WiththegrowthoftheNode.jscommunity,JavaScriptisplayingamoreandmoreimportantrolesinourlife.

TypeScriptisindeedanawesometoolforJavaScript.Unfortunately,intelligenceisstillrequiredtowriteactuallyrobust,maintainable,andreusablecode.TypeScriptallowsdeveloperstowritereadableandmaintainablewebapplications.Editorscanprovideseveraltoolstothedeveloper,basedontypesandstaticanalysisofthecode.

Page 26: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WhatthislearningpathcoversModule1,LearningTypeScript,introducesmanyoftheTypeScriptfeaturesinasimpleandeasy-to-understandformat.Thisbookwillteachyoueverythingyouneedtoknowinordertoimplementlarge-scaleJavaScriptapplicationsusingTypeScript.NotonlydoesitteachTypeScript’scorefeatures,whichareessentialtoimplementawebapplication,butitalsoexploresthepowerofsometools,designprinciples,bestpractices,anditalsodemonstrateshowtoapplytheminareal-lifeapplication.

Module2,TypeScriptDesignPatterns,iscollectionofthemostimportantpatternsyouneedtoimproveyourapplications’performanceandyourproductivity.Eachpatternisaccompaniedwithrichexamplesthatdemonstratethepowerofpatternsforarangeoftasks,frombuildinganapplicationtocodetesting.

Module3,TypeScriptBlueprints,showsyouhowtouseTypeScripttobuildcleanwebapplications.YouwilllearnhowtouseAngular2andReact.YouwillalsolearnhowyoucanuseTypeScriptforservers,mobileapps,command-linetools,andgames.Youwillalsolearnfunctionalprogramming.Thisstyleofprogrammingwillimproveyourgeneralcodeskills.YouwillseehowthisstylecanbeusedinTypeScript.

Page 27: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WhatyouneedforthislearningpathYouwillneedtheTypeScriptcompilerandatexteditor.ThislearningpathexplainshowtouseAtom,butitisalsopossibletouseothereditors,suchasVisualStudio2015,VisualStudioCode,orSublimeText.

YoualsoneedanInternetconnectiontodownloadtherequiredreferencesandonlinepackagesandlibraries,suchasjQuery,Mocha,andGulp.Dependingonyouroperatingsystem,youwillneedauseraccountwithadministrativeprivilegesinordertoinstallsomeofthetoolsusedinthislearningpath.AlsotocompileTypeScript,youneedNodeJS.Youcanfinddetailsonhowyoucaninstallitinthefirstchapterofthethirdmodule.

Page 28: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WhothislearningpathisforThislearningpathisfortheintermediate-levelJavaScriptdevelopersaimingtolearnTypeScripttobuildbeautifulwebapplicationsandfunprojects.NopriorknowledgeofTypeScriptisrequiredbutabasicunderstandingofjQueryisexpected.ThislearningpathisalsoforexperiencedTypeScriptdeveloperwantingtotaketheirskillstothenextlevel,andalsoforwebdeveloperswhowishtomakethemostofTypeScript.

Page 29: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthiscourse—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.

Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthecourse’stitleinthesubjectofyourmessage.

Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 30: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CustomersupportNowthatyouaretheproudownerofaPacktcourse,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 31: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthiscoursefromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthiscourseelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Youcandownloadthecodefilesbyfollowingthesesteps:

1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORT t0thetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthecourseintheSearchbox.5. Selectthecourseforwhichyou’relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthiscoursefrom.7. ClickonCodeDownload.

YoucanalsodownloadthecodefilesbyclickingontheCodeFilesbuttononthecourse’swebpageatthePacktPublishingwebsite.Thispagecanbeaccessedbyenteringthecourse’snameintheSearchbox.PleasenotethatyouneedtobeloggedintoyourPacktaccount.

Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:

WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux

ThecodebundleforthecourseisalsohostedonGitHubathttps://github.com/PacktPublishing/TypeScript-Modern-JavaScript-Development.Wealsohaveothercodebundlesfromourrichcatalogofbooks,videos,andcoursesavailableathttps://github.com/PacktPublishing/.Checkthemout!

Page 32: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourcourses—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthiscourse.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourcourse,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.

Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthecourseinthesearchfield.TherequiredinformationwillappearundertheErratasection.

Page 33: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

Page 34: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

QuestionsIfyouhaveaproblemwithanyaspectofthiscourse,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.

Page 35: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Part1.Module1LearningTypeScript

ExploitthefeaturesofTypeScripttodevelopandmaintaincaptivatingwebapplicationswithease

Page 36: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter1.IntroducingTypeScriptThisbookfocusesonTypeScript'sobject-orientednatureandhowitcanhelpyoutowritebettercode.Beforedivingintotheobject-orientedprogramingfeaturesofTypeScript,thischapterwillgiveyouanoverviewofthehistorybehindTypeScriptandintroduceyoutosomeofthebasics.

Inthischapter,youwilllearnaboutthefollowingconcepts:

TheTypeScriptarchitectureTypeannotationsVariablesandprimitivedatatypesOperatorsFlowcontrolstatementsFunctionsClassesInterfacesModules

Page 37: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheTypeScriptarchitectureInthissection,wewillfocusontheTypeScript'sinternalarchitectureanditsoriginaldesigngoals.

Page 38: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DesigngoalsInthefollowingpoints,youwillfindthemaindesigngoalsandarchitecturaldecisionsthatshapedthewaytheTypeScriptprogramminglanguagelooksliketoday:

StaticallyidentifyJavaScriptconstructsthatarelikelytobeerrors.TheengineersatMicrosoftdecidedthatthebestwaytoidentifyandpreventpotentialruntimeissueswastocreateastronglytypedprogramminglanguageandperformstatictypecheckingatcompilationtime.Theengineersalsodesignedalanguageserviceslayertoprovidedeveloperswithbettertools.HighcompatibilitywiththeexistingJavaScriptcode.TypeScriptisasupersetofJavaScript;thismeansthatanyvalidJavaScriptprogramisalsoavalidTypeScriptprogram(withafewsmallexceptions).Provideastructuringmechanismforlargerpiecesofcode.TypeScriptaddsclass-basedobjectorientation,interfaces,andmodules.Thesefeatureswillhelpusstructureourcodeinamuchbetterway.Wewillalsoreducepotentialintegrationissueswithinourdevelopmentteamandourcodewillbecomemoremaintainableandscalablebyadheringtothebestobject-orientedprinciplesandpractices.Imposenoruntimeoverheadonemittedprograms.ItiscommontodifferentiatebetweendesigntimeandexecutiontimewhenworkingwithTypeScript.WeusethetermdesigntimecodetorefertotheTypeScriptcodethatwewritewhiledesigninganapplication;weusethetermsexecutiontimecodeorruntimecodetorefertotheJavaScriptcodethatisexecutedaftercompilingsomeTypeScriptcode.

TypeScriptaddsfeaturestoJavaScriptbutthosefeaturesareonlyavailableatdesigntime.Forexample,wecandeclareinterfacesinTypeScriptbutsinceJavaScriptdoesn'tsupportinterfaces,theTypeScriptcompilerwillnotdeclareortrytoemulatethisfeatureintheoutputJavaScriptcode.

TheMicrosoftengineersprovidedtheTypeScriptcompilerwithmechanismssuchascodetransformations(convertingTypeScriptfeaturesintoplainJavaScriptimplementations)andtypeerasure(removingstatictypenotation)togeneratereallycleanJavaScriptcode.TypeerasureremovesnotonlythetypeannotationsbutalsoalltheTypeScriptexclusivelanguagefeaturessuchasinterfaces.

Furthermore,thegeneratedcodeishighlycompatiblewithwebbrowsersasittargetstheECMAScript3specificationbydefaultbutitalsosupportsECMAScript5andECMAScript6.Ingeneral,wecanusetheTypeScriptfeatureswhencompilingtoanyoftheavailablecompilationtargets,buttherearesomefeaturesthatwillrequireECMAScript5orhigherasthecompilationtarget.AlignwiththecurrentandfutureECMAScriptproposals.TypeScriptisnotjustcompatiblewiththeexistingJavaScriptcode;itwillalsopotentiallybecompatiblewithfutureversionsofJavaScript.ThemajorityofTypescript'sadditionalfeaturesarebasedonthefutureECMAScriptproposals;thismeansmanyTypeScriptfileswilleventuallybecomevalidJavaScriptfiles.

Page 39: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Beacross-platformdevelopmenttool.MicrosoftreleasedTypeScriptundertheopensourceApachelicenseanditcanbeinstalledandexecutedinallmajoroperatingsystems.

Page 40: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScriptcomponentsTheTypeScriptlanguageisinternallydividedintothreemainlayers.Eachoftheselayersis,inturn,dividedintosublayersorcomponents.Inthefollowingdiagram,wecanseethethreelayers(green,blue,andorange)andeachoftheirinternalcomponents(boxes):

Note

Intheprecedingdiagram,theacronymVSreferstoMicrosoft'sVisualStudio,whichistheofficialintegrateddevelopmentenvironmentforalltheMicrosoftproducts(includingTypeScript).WewilllearnmoreaboutthisandtheotherIDEsinthenextchapter.

Eachofthesemainlayershasadifferentpurpose:

Thelanguage:ItfeaturestheTypeScriptlanguageelements.Thecompiler:Itperformstheparsing,typechecking,andtransformationofyourTypeScriptcodetoJavaScriptcode.Thelanguageservices:ItgeneratesinformationthathelpseditorsandothertoolsprovidebetterassistancefeaturessuchasIntelliSenseorautomatedrefactoring.IDEintegration:InordertotakeadvantagesoftheTypeScriptfeatures,someintegrationworkisrequiredtobedonebythedevelopersoftheIDEs.TypeScriptwasdesignedtofacilitatethedevelopmentoftoolsthathelptoincreasetheproductivityofJavaScriptdevelopers.Asaresultoftheseefforts,integratingTypeScriptwithanIDEisnotacomplicatedtask.AproofofthisisthatthemostpopularIDEsthesedaysincludeagoodTypeScriptsupport.

Note

Inotherbooksandonlineresources,youmayfindreferencestothetermtranspilerinsteadofcompiler.Atranspilerisatypeofcompilerthattakesthesourcecodeofaprogramminglanguageasitsinputandoutputsthesourcecodeintoanotherprogramminglanguagewithmoreorlessthesamelevelofabstraction.

Wedon'tneedtogointoanymoredetailasunderstandinghowtheTypeScriptcompiler

Page 41: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

worksisoutofthescopeofthisbook;however,ifyouwishtolearnmoreaboutthistopic,refertotheTypeScriptlanguagespecification,whichcanbefoundonlineathttp://www.typescriptlang.org/.

Page 42: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScriptlanguagefeaturesNowthatyouhavelearnedaboutthepurposeofTypeScript,it'stimetogetourhandsdirtyandstartwritingsomecode.

BeforeyoucanstartlearninghowtousesomeofthebasicTypeScriptbuildingblocks,youwillneedtosetupyourdevelopmentenvironment.TheeasiestandfastestwaytostartwritingsomeTypeScriptcodeistousetheonlineeditoravailableontheofficialTypeScriptwebsiteathttp://www.typescriptlang.org/Playground,asyoucanseeinthefollowingscreenshot:

Intheprecedingscreenshot,youwillbeabletousethetexteditorontheleft-handsidetowriteyourTypeScriptcode.ThecodeisautomaticallycompiledtoJavaScriptandtheoutputcodewillbeinsertedinthetexteditorlocatedontheright-handsideofthescreen.IfyourTypeScriptcodeisinvalid,theJavaScriptcodeontheright-handsidewillnotberefreshed.

Alternatively,ifyouprefertobeabletoworkoffline,youcandownloadandinstalltheTypeScriptcompiler.IfyouworkwithVisualStudio,youcandownloadtheofficial

Page 43: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScriptextension(version1.5beta)fromhttps://visualstudiogallery.msdn.microsoft.com/107f89a0-a542-4264-b0a9-eb91037cf7af.IfyouareworkingwithVisualStudio2015,youdon'tneedtoinstalltheextensionasVisualStudio2015includesTypeScriptsupportbydefault.

IfyouuseadifferentcodeeditororyouusetheOSXorLinuxoperatingsystems,youcandownloadannpmmoduleinstead.Don'tworryifyouarenotfamiliarwithnpm.Fornow,youjustneedtoknowthatitstandsforNodePackageManagerandisthedefaultNode.jspackagemanager.

Note

ThereareTypeScriptpluginsavailableformanypopulareditorssuchasSublimehttps://github.com/Microsoft/TypeScript-Sublime-PluginandAtomhttps://atom.io/packages/atom-typescript.

Inordertobeabletousenpm,youwillneedtofirstinstallNode.jsinyourdevelopmentenvironment.YouwillbeabletofindtheNode.jsinstallationfilesontheofficialwebsiteathttps://nodejs.org/.

OnceyouhaveinstalledNode.jsinyourdevelopmentenvironment,youwillbeabletorunthefollowingcommandinaconsoleorterminal:

npminstall-gtypescript

OSXusersneedtousethesudocommandwheninstallingglobal(-g)npmpackages.Thesudocommandwillpromptforusercredentialsandinstallthepackageusingadministrativeprivileges:

sudonpminstall-gtypescript

Createanewfilenamedtest.tsandaddthefollowingcodetoit:

vart:number=1;

Savethefileintoadirectoryofyourchoiceandonceyouhavesavedthefileopentheconsole,selectthedirectorywhereyousavedthefile,andexecutethefollowingcommand:

tsctest.ts

ThetsccommandisaconsoleinterfacefortheTypeScriptcompiler.ThiscommandallowsyoutocompileyourTypeScriptfilesintoJavaScriptfiles.Thecompilerfeaturesmanyoptionsthatwillbeexploredintheupcomingchaptersofthisbook.

Intheprecedingexample,weusedthetsccommandtotransformthetest.tsfileintoaJavaScriptfile.

Ifeverythinggoesright,youwillfindafilenamedtest.jsinthesamedirectoryinwhichthe

Page 44: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

test.tsfilewaslocated.Now,youknowhowtocompileyourTypeScriptcodeintoJavaScriptandwecanstartlearningabouttheTypeScriptfeatures.

Note

YouwillbeabletolearnmoreabouteditorsandothertoolsinChapter2,AutomatingYourDevelopmentWorkflow.

Page 45: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypesAswehavealreadylearned,TypeScriptisatypedsupersetofJavaScript.TypeScriptaddedoptionalstatictypeannotationstoJavaScriptinordertotransformitintoastronglytypedprogramminglanguage.Theoptionalstatictypeannotationsareusedasconstraintsonprogramentitiessuchasfunctions,variables,andpropertiessothatcompilersanddevelopmenttoolscanofferbetterverificationandassistance(suchasIntelliSense)duringsoftwaredevelopment.

Strongtypingallowstheprogrammertoexpresshisintentionsinhiscode,bothtohimselfandtoothersinthedevelopmentteam.

Typescript'stypeanalysisoccursentirelyatcompiletimeandaddsnoruntimeoverheadtoprogramexecution.

Optionalstatictypenotation

TheTypeScriptlanguageserviceisreallygoodatinferringtypes,buttherearecertaincaseswhereitisnotabletoautomaticallydetectthetypeofanobjectorvariable.Forthesecases,TypeScriptallowsustoexplicitlydeclarethetypeofavariable.Thelanguageelementthatallowsustodeclarethetypeofavariableisknownasoptionalstatictypenotation.Foravariable,thetypenotationcomesafterthevariablenameandisprecededbyacolon:

varcounter;//unknown(any)type

varcounter=0;//number(inferred)

varcounter:number;//number

varcounter:number=0;//number

Asyoucansee,thetypeofthevariableisdeclaredafterthename,thisstyleoftypenotationisbasedontypetheoryandhelpstoreinforcetheideaoftypesbeingoptional.Whennotypeannotationsareavailable,TypeScriptwilltrytoguessthetypeofthevariablebyexaminingtheassignedvalues.Forexample,inthesecondlineintheprecedingcodesnippet,wecanseethatthevariablecounterhasbeenidentifiedasanumericvariablebecausethenumericvalue0wasassignedasitsvalue.ThisprocessinwhichtypesareautomaticallydetectedisknownasTypeinference,whenatypecannotbeinferredtheespecialtypeanyisusedasthetypeofthevariable.

Page 46: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Variables,basictypes,andoperatorsThebasictypesaretheBoolean,number,string,array,voidtypes,andalluserdefinedEnumtypes.AlltypesinTypeScriptaresubtypesofasingletoptypecalledtheAnytype.Theanykeywordreferencesthistype.Let'stakealookateachoftheseprimitivetypes:

DataType Description

Boolean

Whereasthestringandnumberdatatypescanhaveavirtuallyunlimitednumberofdifferentvalues,theBooleandatatypecanonlyhavetwo.Theyaretheliteralstrueandfalse.ABooleanvalueisatruthvalue;itspecifieswhethertheconditionistrueornot.

varisDone:boolean=false;

Number

AsinJavaScript,allnumbersinTypeScriptarefloatingpointvalues.Thesefloating-pointnumbersgetthetypenumber.

varheight:number=6;

String

YouusethestringdatatypetorepresenttextinTypeScript.Youincludestringliteralsinyourscriptsbyenclosingtheminsingleordoublequotationmarks.Doublequotationmarkscanbecontainedinstringssurroundedbysinglequotationmarks,andsinglequotationmarkscanbecontainedinstringssurroundedbydoublequotationmarks.

varname:string="bob";

name='smith';

Array

TypeScript,likeJavaScript,allowsyoutoworkwitharraysofvalues.Arraytypescanbewritteninoneofthetwoways.Inthefirst,youusethetypeoftheelementsfollowedby[]todenoteanarrayofthatelementtype:

varlist:number[]=[1,2,3];

Thesecondwayusesagenericarraytype,Array:

varlist:Array<number>=[1,2,3];

Anenumisawayofgivingmorefriendlynamestosetsofnumericvalues.Bydefault,enumsbeginnumberingtheirmembersstartingat0,butyoucanchange

Page 47: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Enum thisbymanuallysettingthevalueofonetoitsmembers.

enumColor{Red,Green,Blue};

varc:Color=Color.Green;

Any

TheanytypeisusedtorepresentanyJavaScriptvalue.AvalueoftheanytypesupportsthesameoperationsasavalueinJavaScriptandminimalstatictypecheckingisperformedforoperationsonanyvalues.

varnotSure:any=4;

notSure="maybeastringinstead";

notSure=false;//okay,definitelyaboolean

TheanytypeisapowerfulwaytoworkwithexistingJavaScript,allowingyoutograduallyoptinandoptoutoftypecheckingduringcompilation.Theanytypeisalsohandyifyouknowsomepartofthetype,butperhapsnotallofit.Forexample,youmayhaveanarraybutthearrayhasamixofdifferenttypes:

varlist:any[]=[1,true,"free"];

list[1]=100;

Void

Theoppositeinsomewaystoanyisvoid,theabsenceofhavinganytypeatall.Youwillseethisasthereturntypeoffunctionsthatdonotreturnavalue.

functionwarnUser():void{

alert("Thisismywarningmessage");

}

JavaScript'sprimitivetypesalsoincludeundefinedandnull.InJavaScript,undefinedisapropertyintheglobalscopethatisassignedasavaluetovariablesthathavebeendeclaredbuthavenotyetbeeninitialized.Thevaluenullisaliteral(notapropertyoftheglobalobject).Itcanbeassignedtoavariableasarepresentationofnovalue.

varTestVar;//variableisdeclaredbutnotinitialized

alert(TestVar);//showsundefined

alert(typeofTestVar);//showsundefined

varTestVar=null;//variableisdeclaredandvaluenullisassignedas

value

alert(TestVar);//showsnull

alert(typeofTestVar);//showsobject

InTypeScript,wewillnotbeabletousenullorundefinedastypes:

varTestVar:null;//Error,Typeexpected

varTestVar:undefined;//Error,cannotfindnameundefined

Page 48: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Sincenullorundefinedcannotbeusedastypes,boththevariabledeclarationsintheprecedingcodesnippetareinvalid.

Var,let,andconst

WhenwedeclareavariableinTypeScript,wecanusethevar,let,orconstkeywords:

varmynum:number=1;

letisValid:boolean=true;

constapiKey:string="0E5CE8BD-6341-4CC2-904D-C4A94ACD276E";

Variablesdeclaredwithvararescopedtothenearestfunctionblock(orglobal,ifoutsideafunctionblock).

Variablesdeclaredwithletarescopedtothenearestenclosingblock(orglobalifoutsideanyblock),whichcanbesmallerthanafunctionblock.

Theconstkeywordcreatesaconstantthatcanbeglobalorlocaltotheblockinwhichitisdeclared.Thismeansthatconstantsareblockscoped.YouwilllearnmoreaboutscopesinChapter5,Runtime.

Note

TheletandconstkeywordshavebeenavailablesincethereleaseofTypeScript1.4butonlywhenthecompilationtargetisECMAScript6.However,theywillalsoworkwhentargetingECMAScript3andECMAScript5onceTypeScript1.5isreleased.

Uniontypes

TypeScriptallowsyoutodeclareuniontypes:

varpath:string[]|string;

path='/temp/log.xml';

path=['/temp/log.xml','/temp/errors.xml'];

path=1;//Error

Uniontypesareusedtodeclareavariablethatisabletostoreavalueoftwoormoretypes.Intheprecedingexample,wehavedeclaredavariablenamedpaththatcancontainasinglepath(string),oracollectionofpaths(arrayofstring).Intheexample,wehavealsosetthevalueofthevariable.Weassignedastringandanarrayofstringswithouterrors;however,whenweattemptedtoassignanumericvalue,wegotacompilationerrorbecausetheuniontypedidn'tdeclareanumberasoneofthevalidtypesofthevariable.

Typeguards

Wecanexaminethetypeofanexpressionatruntimebyusingthetypeoforinstanceofoperators.TheTypeScriptlanguageservicelooksfortheseoperatorsandwillchangetypeinferenceaccordinglywhenusedinanifblock:

Page 49: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

varx:any={/*...*/};

if(typeofx==='string'){

console.log(x.splice(3,1));//Error,'splice'doesnotexiston'string'

}

//xisstillany

x.foo();//OK

Intheprecedingcodesnippet,wehavedeclaredanxvariableoftypeany.Later,wecheckthetypeofxatruntimebyusingthetypeofoperator.Ifthetypeofxresultstobestring,wewilltrytoinvokethemethodsplice,whichissupposedtoamemberofthexvariable.TheTypeScriptlanguageserviceisabletounderstandtheusageoftypeofinaconditionalstatement.TypeScriptwillautomaticallyassumethatxmustbeastringandletusknowthatthesplicemethoddoesnotexistonthetypestring.Thisfeatureisknownastypeguards.

Typealiases

TypeScriptallowsustodeclaretypealiasesbyusingthetypekeyword:

typePrimitiveArray=Array<string|number|boolean>;

typeMyNumber=number;

typeNgScope=ng.IScope;

typeCallback=()=>void;

Typealiasesareexactlythesameastheiroriginaltypes;theyaresimplyalternativenames.Typealiasescanhelpustomakeourcodemorereadablebutitcanalsoleadtosomeproblems.

Ifyouworkaspartofalargeteam,theindiscriminatecreationofaliasescanleadtomaintainabilityproblems.Inthebook,MaintainableJavaScript,NicholasC.Zakas,theauthorrecommendstoavoidmodifyingobjectsyoudon'town.Nicholaswastalkingaboutadding,removing,oroverridingmethodsinobjectsthathavenotbeendeclaredbyyou(DOMobjects,BOMobjects,primitivetypes,andthird-partylibraries)butwecanapplythisruletotheusageofaliasesaswell.

Ambientdeclarations

AmbientdeclarationallowsyoutocreateavariableinyourTypeScriptcodethatwillnotbetranslatedintoJavaScriptatcompilationtime.ThisfeaturewasdesignedtofacilitateintegrationwiththeexistingJavaScriptcode,theDOM(DocumentObjectModel),andBOM(BrowserObjectModel).Let'stakealookatanexample:

customConsole.log("Alogentry!");//error

IfyoutrytocallthememberlogofanobjectnamedcustomConsole,TypeScriptwillletusknowthatthecustomConsoleobjecthasnotbeendeclared:

//Cannotfindname'customConsole'

Thisisnotasurprise.However,sometimeswewanttoinvokeanobjectthathasnotbeen

Page 50: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

defined,forexample,theconsoleorwindowobjects.

console.log("LogEntry!");

varhost=window.location.hostname;

WhenweaccessDOMorBOMobjects,wedon'tgetanerrorbecausetheseobjectshavealreadybeendeclaredinaspecialTypeScriptfileknownasdeclarationfiles.Youcanusethedeclareoperatortocreateanambientdeclaration.

Inthefollowingcodesnippet,wewilldeclareaninterfacethatisimplementedbythecustomConsoleobject.WethenusethedeclareoperatortoaddthecustomConsoleobjecttothescope:

interfaceICustomConsole{

log(arg:string):void;

}

declarevarcustomConsole:ICustomConsole;

Note

Interfacesareexplainedingreaterdetaillaterinthechapter.

WecanthenusethecustomConsoleobjectwithoutcompilationerrors:

customConsole.log("Alogentry!");//ok

TypeScriptincludes,bydefault,afilenamedlib.d.tsthatprovidesinterfacedeclarationsforthebuilt-inJavaScriptlibraryaswellastheDOM.

Declarationfilesusethefileextension.d.tsandareusedtoincreasetheTypeScriptcompatibilitywiththird-partylibrariesandrun-timeenvironmentssuchasNode.jsorawebbrowser.

Note

WewilllearnhowtoworkwithdeclarationfilesinChapter2,AutomatingYourDevelopmentWorkflow.

Arithmeticoperators

TherefollowingarithmeticoperatorsaresupportedbytheTypeScriptprogramminglanguage.Inordertounderstandtheexamples,youmustassumethatvariableAholds10andvariableBholds20.

Operator Description Example

+ Thisaddstwooperands A+Bwillgive30

Page 51: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

- Thissubtractsthesecondoperandfromthefirst A-Bwillgive-10

* Thismultipliesboththeoperands A*Bwillgive200

/ Thisdividesthenumeratorbythedenominator B/Awillgive2

% Thisisthemodulusoperatorandremainderafteranintegerdivision B%Awillgive0

++ Thisistheincrementoperatorthatincreasestheintegervalueby1 A++willgive11

-- Thisisthedecrementoperatorthatdecreasestheintegervalueby1 A--willgive9

Comparisonoperators

ThefollowingcomparisonoperatorsaresupportedbytheTypeScriptlanguage.Inordertounderstandtheexamples,youmustassumethatvariableAholds10andvariableBholds20.

Operator Description Example

== Thischeckswhetherthevaluesoftwooperandsareequalornot.Ifyes,thentheconditionbecomestrue.

(A==B)isfalse.A=="10"istrue.

=== Thischeckswhetherthevalueandtypeoftwooperandsareequalornot.Ifyes,thentheconditionbecomestrue.

A===Bisfalse.A==="10"isfalse.

!= Thischeckswhetherthevaluesoftwooperandsareequalornot.Ifthevaluesarenotequal,thentheconditionbecomestrue. (A!=B)istrue.

>Thischeckswhetherthevalueoftheleftoperandisgreaterthanthevalueoftherightoperand;ifyes,thentheconditionbecomes (A>B)isfalse.

Page 52: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

true.

<Thischeckswhetherthevalueoftheleftoperandislessthanthevalueoftherightoperand;ifyes,thentheconditionbecomestrue.

(A<B)istrue.

>=Thischeckswhetherthevalueoftheleftoperandisgreaterthanorequaltothevalueoftherightoperand;ifyes,thentheconditionbecomestrue.

(A>=B)isfalse.

<=Thischeckswhetherthevalueoftheleftoperandislessthanorequaltothevalueoftherightoperand;ifyes,thentheconditionbecomestrue.

(A<=B)istrue.

Logicaloperators

ThefollowinglogicaloperatorsaresupportedbytheTypeScriptlanguage.Inordertounderstandtheexamples,youmustassumethatvariableAholds10andvariableBholds20.

Operator Description Example

&& ThisiscalledthelogicalANDoperator.Ifboththeoperandsarenonzero,thentheconditionbecomestrue.

(A&&B)istrue.

|| ThisiscalledlogicalORoperator.Ifanyofthetwooperandsarenonzero,thentheconditionbecomestrue.

(A||B)istrue.

!ThisiscalledthelogicalNOToperator.Itisusedtoreversethelogicalstateofitsoperand.Ifaconditionistrue,thenthelogicalNOToperatorwillmakeitfalse.

!(A&&B)isfalse.

Bitwiseoperators

ThefollowingbitwiseoperatorsaresupportedbytheTypeScriptlanguage.Inordertounderstandtheexamples,youmustassumethatvariableAholds2andvariableBholds3.

Operator Description Example

Page 53: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

& ThisiscalledtheBitwiseANDoperator.ItperformsaBooleanANDoperationoneachbitofitsintegerarguments.

(A&B)is2

| ThisiscalledtheBitwiseORoperator.ItperformsaBooleanORoperationoneachbitofitsintegerarguments.

(A|B)is3.

^

ThisiscalledtheBitwiseXORoperator.ItperformsaBooleanexclusiveORoperationoneachbitofitsintegerarguments.ExclusiveORmeansthateitheroperandoneistrueoroperandtwoistrue,butnotboth.

(A^B)is1.

~ ThisiscalledtheBitwiseNOToperator.Itisaunaryoperatorandoperatesbyreversingallbitsintheoperand.

(~B)is-4

<<

ThisiscalledtheBitwiseShiftLeftoperator.Itmovesallbitsinitsfirstoperandtotheleftbythenumberofplacesspecifiedinthesecondoperand.Newbitsarefilledwithzeros.Shiftingavalueleftbyonepositionisequivalenttomultiplyingby2,shiftingtwopositionsisequivalenttomultiplyingby4,andsoon.

(A<<1)is4

>>ThisiscalledtheBitwiseShiftRightwithsignoperator.Itmovesallbitsinitsfirstoperandtotherightbythenumberofplacesspecifiedinthesecondoperand.

(A>>1)is1

>>>ThisiscalledtheBitwiseShiftRightwithzerooperators.Thisoperatorisjustlikethe>>operator,exceptthatthebitsshiftedinontheleftarealwayszero,

(A>>>1)is1

Note

OneofthemainreasonstousebitwiseoperatorsinlanguagessuchasC++,Java,orC#isthatthey'reextremelyfast.However,bitwiseoperatorsareoftenconsiderednotthatefficientinTypeScriptandJavaScript.BitwiseoperatorsarelessefficientinJavaScriptbecauseitisnecessarytocastfromfloatingpointrepresentation(howJavaScriptstoresallofitsnumbers)toa32-bitintegertoperformthebitmanipulationandback.

Assignmentoperators

ThefollowingassignmentoperatorsaresupportedbytheTypeScriptlanguage.

Operator Description Example

Page 54: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

= Thisisasimpleassignmentoperatorthatassignsvaluesfromtheright-sideoperandstotheleft-sideoperand.

C=A+BwillassignthevalueofA+BintoC

+=ThisaddstheANDassignmentoperator.Itaddstherightoperandtotheleftoperandandassignstheresulttotheleftoperand.

C+=AisequivalenttoC=C+A

-=ThissubtractstheANDassignmentoperator.Itsubtractstherightoperandfromtheleftoperandandassignstheresulttotheleftoperand.

C-=AisequivalenttoC=C-A

*=ThismultipliestheANDassignmentoperator.Itmultipliestherightoperandwiththeleftoperandandassignstheresulttotheleftoperand.

C*=AisequivalenttoC=C*A

/=ThisdividestheANDassignmentoperator.Itdividestheleftoperandwiththerightoperandandassignstheresulttotheleftoperand.

C/=AisequivalenttoC=C/A

%=ThisisthemodulusANDassignmentoperator.Ittakesthemodulususingtwooperandsandassignstheresulttotheleftoperand.

C%=AisequivalenttoC=C%A

Page 55: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FlowcontrolstatementsThissectiondescribesthedecision-makingstatements,theloopingstatements,andthebranchingstatementssupportedbytheTypeScriptprogramminglanguage.

Thesingle-selectionstructure(if)

ThefollowingcodesnippetdeclaresavariableoftypeBooleanandnameisValid.Then,anifstatementwillcheckwhetherthevalueofisValidisequaltotrue.Ifthestatementturnsouttobetrue,theIsvalid!messagewillbedisplayedonthescreen.

varisValid:boolean=true;

if(isValid){

alert("isvalid!");

}

Thedouble-selectionstructure(if…else)

ThefollowingcodesnippetdeclaresavariableoftypeBooleanandnameisValid.Then,anifstatementwillcheckwhetherthevalueofisValidisequaltotrue.Ifthestatementturnsouttobetrue,themessageIsvalid!willbedisplayedonthescreen.Ontheotherside,ifthestatementturnsouttobefalse,themessageIsNOTvalid!willbedisplayedonthescreen.

varisValid:boolean=true;

if(isValid){

alert("Isvalid!");

}

else{

alert("IsNOTvalid!");

}

Theinlineternaryoperator(?)

Theinlineternaryoperatorisjustanalternativewayofdeclaringadouble-selectionstructure.

varisValid:boolean=true;

varmessage=isValid?"Isvalid!":"IsNOTvalid!";

alert(message);

TheprecedingcodesnippetdeclaresavariableoftypeBooleanandnameisValid.Thenitcheckswhetherthevariableorexpressionontheleft-handsideoftheoperator?isequaltotrue.

Ifthestatementturnsouttobetrue,theexpressionontheleft-handsideofthecharacterwillbeexecutedandthemessageIsvalid!willbeassignedtothemessagevariable.

Ontheotherhand,ifthestatementturnsouttobefalse,theexpressionontheright-handsideoftheoperatorwillbeexecutedandthemessage,IsNOTvalid!willbeassignedtothe

Page 56: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

messagevariable.

Finally,thevalueofthemessagevariableisdisplayedonthescreen.

Themultiple-selectionstructure(switch)

Theswitchstatementevaluatesanexpression,matchestheexpression'svaluetoacaseclause,andexecutesstatementsassociatedwiththatcase.Aswitchstatementandenumerationsareoftenusedtogethertoimprovethereadabilityofthecode.

Inthefollowingexample,wewilldeclareafunctionthattakesanenumerationAlertLevel.Insidethefunction,wewillgenerateanarrayofstringstostoree-mailaddressesandexecuteaswitchstructure.Eachoftheoptionsoftheenumerationisacaseintheswitchstructure:

enumAlertLevel{

info,

warning,

error

}

functiongetAlertSubscribers(level:AlertLevel){

varemails=newArray<string>();

switch(level){

caseAlertLevel.info:

emails.push("[email protected]");

break;

caseAlertLevel.warning:

emails.push("[email protected]");

emails.push("[email protected]");

break;

caseAlertLevel.error:

emails.push("[email protected]");

emails.push("[email protected]");

emails.push("[email protected]");

break;

default:

thrownewError("Invalidargument!");

}

returnemails;

}

getAlertSubscribers(AlertLevel.info);//["[email protected]"]

getAlertSubscribers(AlertLevel.warning);//["[email protected]",

"[email protected]"]

Thevalueofthelevelvariableistestedagainstallthecasesintheswitch.Ifthevariablematchesoneofthecases,thestatementassociatedwiththatcaseisexecuted.Oncethecasestatementhasbeenexecuted,thevariableistestedagainstthenextcase.

Oncetheexecutionofthestatementassociatedtoamatchingcaseisfinalized,thenextcasewillbeevaluated.Ifthebreakkeywordispresent,theprogramwillnotcontinuetheexecutionofthefollowingcasestatement.

Page 57: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Ifnomatchingcaseclauseisfound,theprogramlooksfortheoptionaldefaultclause,andiffound,ittransferscontroltothatclauseandexecutestheassociatedstatements.

Ifnodefaultclauseisfound,theprogramcontinuesexecutionatthestatementfollowingtheendofswitch.Byconvention,thedefaultclauseisthelastclause,butitdoesnothavetobeso.

Theexpressionistestedatthetopoftheloop(while)

Thewhileexpressionisusedtorepeatanoperationwhileacertainrequirementissatisfied.Forexample,thefollowingcodesnippet,declaresanumericvariablei.Iftherequirement(thevalueofiislessthan5)issatisfied,anoperationtakesplace(increasethevalueofiby1anddisplayitsvalueinthebrowserconsole).Oncetheoperationhascompleted,theaccomplishmentoftherequirementwillbecheckedagain.

vari:number=0;

while(i<5){

i+=1;

console.log(i);

}

Inawhileexpression,theoperationwilltakeplaceonlyiftherequirementissatisfied.

Theexpressionistestedatthebottomoftheloop(do…while)

Thedo-whileexpressionisusedtorepeatanoperationuntilacertainrequirementisnotsatisfied.Forexample,thefollowingcodesnippetdeclaresanumericvariableiandrepeatsanoperation(increasethevalueofiby1anddisplayitsvalueinthebrowserconsole)foraslongastherequirement(thevalueofiislessthan5)issatisfied.

vari:number=0;

do{

i+=1;

console.log(i);

}while(i<5);

Unlikethewhileloop,thedo-whileexpressionwillexecuteatleastonceregardlessoftherequirementvalueastheoperationwilltakeplacebeforecheckingifacertainrequirementissatisfiedornot.

Iterateoneachobject'sproperties(for…in)

Thefor-instatementbyitselfisnotabadpractice;however,itcanbemisused,forexample,toiterateoverarraysorarray-likeobjects.Thepurposeofthefor-instatementistoenumerateoverobjectproperties.

varobj:any={a:1,b:2,c:3};

for(varkeyinobj){

console.log(key+"="+obj[key]);

}

Page 58: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

//Output:

//"a=1"

//"b=2"

//"c=3"

Thefollowingcodesnippetwillgoupintheprototypechain,alsoenumeratingtheinheritedproperties.Thefor-instatementiteratestheentireprototypechain,alsoenumeratingtheinheritedproperties.Whenyouwanttoenumerateonlytheobject'sownproperties(theonesthataren'tinherited),youcanusethehasOwnPropertymethod:

for(varkeyinobj){

if(obj.hasOwnProperty(prop)){

//propisnotinherited

}

}

Countercontrolledrepetition(for)

Theforstatementcreatesaloopthatconsistsofthreeoptionalexpressions,enclosedinparenthesesandseparatedbysemicolons,followedbyastatementorasetofstatementsexecutedintheloop.

for(vari:number=0;i<9;i++){

console.log(i);

}

Theprecedingcodesnippetcontainsaforstatement,itstartsbydeclaringthevariableiandinitializingitto0.Itcheckswhetheriislessthan9,performsthetwosucceedingstatements,andincrementsiby1aftereachpassthroughtheloop.

Page 59: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionsJustasinJavaScript,TypeScriptfunctionscanbecreatedeitherasanamedfunctionorasananonymousfunction.Thisallowsustochoosethemostappropriateapproachforanapplication,whetherwearebuildingalistoffunctionsinanAPIoraone-offfunctiontohandovertoanotherfunction.

//namedfunction

functiongreet(name?:string):string{

if(name){

return"Hi!"+name;

}

else

{

return"Hi!";

}

}

//anonymousfunction

vargreet=function(name?:string):string{

if(name){

return"Hi!"+name;

}

else

{

return"Hi!";

}

}

Aswecanseeintheprecedingcodesnippet,inTypeScriptwecanaddtypestoeachoftheparametersandthentothefunctionitselftoaddareturntype.TypeScriptcaninferthereturntypebylookingatthereturnstatements,sowecanalsooptionallyleavethisoffinmanycases.

Thereisanalternativefunctionsyntax,whichusesthearrow(=>)operatorafterthefunction’sreturntypeandskipstheusageofthefunctionkeyword.

vargreet=(name:string):string=>{

if(name){

return"Hi!"+name;

}

else

{

return"Hi!mynameis"+this.fullname;

}

};

Thefunctionsdeclaredusingthissyntaxarecommonlyknownasarrowfunctions.Let'sreturntothepreviousexampleinwhichwewereassigningananonymousfunctiontothegreetvariable.Wecannowaddthetypeannotationstothegreetvariabletomatchtheanonymousfunctionsignature.

Page 60: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

vargreet:(name:string)=>string=function(name:string):string{

if(name){

return"Hi!"+name;

}

else

{

return"Hi!";

}

};

Note

Keepinmindthatthearrowfunction(=>)syntaxchangesthewaythethisoperatorworkswhenworkingwithclasses.Wewilllearnmoreaboutthisintheupcomingchapters.

Nowyouknowhowtoaddtypeannotationstoforceavariabletobeafunctionwithaspecificsignature.Theusageofthiskindofannotationsisreallycommonwhenweuseacallback(functionsusedasanargumentofanotherfunction).

functionsume(a:number,b:number,callback:(result:number)=>void){

callback(a+b);

}

Intheprecedingexample,wearedeclaringafunctionnamedsumethattakestwonumbersandacallbackasafunction.Thetypeannotationswillforcethecallbacktoreturnvoidandtakeanumberasitsonlyargument.

Note

WewillfocusonfunctionsinChapter3,WorkingwithFunctions.

Page 61: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ClassesECMAScript6,thenextversionofJavaScript,addsclass-basedobjectorientationtoJavaScriptand,sinceTypeScriptisbasedonES6,developersareallowedtouseclass-basedobjectorientationtoday,andcompilethemdowntoJavaScriptthatworksacrossallmajorbrowsersandplatforms,withouthavingtowaitforthenextversionofJavaScript.

Let'stakealookatasimpleTypeScriptclassdefinitionexample:

classCharacter{

fullname:string;

constructor(firstname:string,lastname:string){

this.fullname=firstname+""+lastname;

}

greet(name?:string){

if(name)

{

return"Hi!"+name+"!mynameis"+this.fullname;

}

else

{

return"Hi!mynameis"+this.fullname;

}

}

}

varspark=newCharacter("Jacob","Keyes");

varmsg=spark.greet();

alert(msg);//"Hi!mynameisJacobKeyes"

varmsg1=spark.greet("Dr.Halsey");

alert(msg1);//"Hi!Dr.Halsey!mynameisJacobKeyes"

Intheprecedingexample,wehavedeclaredanewclassCharacter.Thisclasshasthreemembers:apropertycalledfullname,aconstructor,andamethodgreet.WhenwedeclareaclassinTypeScript,allthemethodsandpropertiesarepublicbydefault.

You'llnoticethatwhenwerefertooneofthemembersoftheclass(fromwithinitself)weprependthethisoperator.Thethisoperatordenotesthatit'samemberaccess.Inthelastlines,weconstructaninstanceoftheCharacterclassusinganewoperator.Thiscallsintotheconstructorwedefinedearlier,creatinganewobjectwiththeCharactershape,andrunningtheconstructortoinitializeit.

TypeScriptclassesarecompiledintoJavaScriptfunctionsinordertoachievecompatibilitywithECMAScript3andECMAScript5.

Note

Wewilllearnmoreaboutclassesandotherobject-orientedprogrammingconceptsinChapter4,Object-OrientedProgrammingwithTypeScript.

Page 62: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InterfacesInTypeScript,wecanuseinterfacestoenforcethataclassfollowthespecificationinaparticularcontract.

interfaceLoggerInterface{

log(arg:any):void;

}

classLoggerimplementsLoggerInterface{

log(arg){

if(typeofconsole.log==="function"){

console.log(arg);

}

else

{

alert(arg);

}

}

}

Intheprecedingexample,wehavedefinedaninterfaceloggerInterfaceandaclassLogger,whichimplementsit.TypeScriptwillalsoallowyoutouseinterfacestodeclarethetypeofanobject.Thiscanhelpustopreventmanypotentialissues,especiallywhenworkingwithobjectliterals:

interfaceUserInterface{

name:string;

password:string;

}

varuser:UserInterface={

name:"",

password:""//errorpropertypasswordismissing

};

Note

Wewilllearnmoreaboutinterfacesandotherobject-orientedprogrammingconceptsinChapter4,Object-OrientedProgrammingwithTypeScript.

Page 63: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NamespacesNamespaces,alsoknownasinternalmodules,areusedtoencapsulatefeaturesandobjectsthatshareacertainrelationship.Namespaceswillhelpyoutoorganizeyourcodeinamuchclearerway.TodeclareanamespaceinTypeScript,youwillusethenamespaceandexportkeywords.

namespaceGeometry{

interfaceVectorInterface{

/*...*/

}

exportinterfaceVector2dInterface{

/*...*/

}

exportinterfaceVector3dInterface{

/*...*/

}

exportclassVector2dimplementsVectorInterface,Vector2dInterface{

/*...*/

}

exportclassVector3dimplementsVectorInterface,Vector3dInterface{

/*...*/

}

}

varvector2dInstance:Geometry.Vector2dInterface=newGeometry.Vector2d();

varvector3dInstance:Geometry.Vector3dInterface=newGeometry.Vector3d();

Intheprecedingcodesnippet,wehavedeclaredanamespacethatcontainstheclassesvector2dandvector3dandtheinterfacesVectorInterface,Vector2dInterface,andVector3dInterface.Notethatthefirstinterfaceismissingthekeywordexport.Asaresult,theinterfaceVectorInterfacewillnotbeaccessiblefromoutsidethenamespace'sscope.

Note

InChapter4,Object-OrientedProgrammingwithTypeScript,we'llbecoveringnamespaces(internalmodules)andexternalmodulesandwe'lldiscusswheneachisappropriateandhowtousethem.

Page 64: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PuttingeverythingtogetherNowthatwehavelearnedhowtousethebasicTypeScriptbuildingblocksindividually,let'stakealookatafinalexampleinwhichwewillusemodules,classes,functions,andtypeannotationsforeachoftheseelements:

moduleGeometry{

exportinterfaceVector2dInterface{

toArray(callback:(x:number[])=>void):void;

length():number;

normalize();

}

exportclassVector2dimplementsVector2dInterface{

private_x:number;

private_y:number;

constructor(x:number,y:number){

this._x=x;

this._y=y;

}

toArray(callback:(x:number[])=>void):void{

callback([this._x,this._y]);

}

length():number{

returnMath.sqrt(this._x*this._x+this._y*this._y);

}

normalize(){

varlen=1/this.length();

this._x*=len;

this._y*=len;

}

}

}

Theprecedingexampleisjustasmallportionofabasic3DenginewritteninJavaScript.In3Dengines,therearealotofmathematicalcalculationsinvolvingmatricesandvectors.Asyoucansee,wehavedefinedamoduleGeometrythatwillcontainsomeentities;tokeeptheexamplesimple,wehaveonlyaddedtheclassVector2d.Thisclassstorestwocoordinates(xandy)in2dspaceandperformssomeoperationsonthecoordinates.Oneofthemostusedoperationsonvectorsisnormalization,whichisoneofthemethodsinourVector2dclass.

3Denginesarecomplexsoftwaresolutions,andasadeveloper,youaremuchmorelikelytouseathird-party3Denginethancreateyourown.Forthisreason,itisimportanttounderstandthatTypeScriptwillnotonlyhelpyoutodeveloplarge-scaleapplications,butalsotoworkwithlarge-scaleapplications.Inthefollowingcodesnippet,wewillusethemoduledeclaredearliertocreateaVector2dinstance:

varvector:Geometry.Vector2dInterface=newGeometry.Vector2d(2,3);

vector.normalize();

vector.toArray(function(vectorAsArray:number[]){

alert('x:'+vectorAsArray[0]+'y:'+vectorAsArray[1]);

});

Page 65: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThetypecheckingandIntelliSensefeatureswillhelpuscreateaVector2dinstance,normalizeitsvalue,andconvertitintoanarraytofinallyshowitsvalueonscreenwithease.

Page 66: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,youhavelearnedaboutthepurposesofTypeScript.YouhavealsolearnedaboutsomeofthedesigndecisionsmadebytheTypeScriptengineersatMicrosoft.

Towardstheendofthischapter,youlearnedalotaboutthebasicbuildingblocksofaTypeScriptapplication.YoustartedtowritesomeTypeScriptcodeforthefirsttimeandyoucannowworkwithtypeannotations,variablesandprimitivedatatypes,operators,flowcontrolstatements,functions,andclasses.

Inthenextchapter,youwilllearnhowtoautomateyourdevelopmentworkflow.

Page 67: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter2.AutomatingYourDevelopmentWorkflowAftertakingafirstlookatthemainTypeScriptlanguagefeatures,wewillnowlearnhowtousesometoolstoautomateourdevelopmentworkflow.Thesetoolswillhelpustoreducetheamountoftimethatweusuallyspendonsimpleandrepetitivetasks.

Inthischapter,wewilllearnaboutthefollowingtopics:

AnoverviewofthedevelopmentworkflowSourcecontroltoolsPackagemanagementtoolsTaskrunnersTestrunnersIntegrationtoolsScaffoldingtools

Page 68: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AmoderndevelopmentworkflowDevelopingawebapplicationwithhighqualitystandardshasbecomeatime-consumingactivity.Ifwewanttoachieveagreatuserexperience,wewillneedtoensurethatourapplicationscanrunassmoothlyaspossibleonmanydifferentwebbrowsers,devices,Internetconnectionspeeds,andscreenresolutions.Furthermore,wewillneedtospendalotofourtimeworkingonqualityassuranceandperformanceoptimizationtasks.

Asdevelopers,weshouldtrytominimizethetimespentonsimpleandrepetitivetasks.Thismightsoundfamiliaraswehavebeendoingthisforyears.Westartedbywritingbuildscripts(suchasmakefiles)orautomatedtestsandtoday,inamodernwebdevelopmentworkflow,weusemanytoolstotrytoautomateasmanytasksaswecan.Thesetoolscanbecategorizedintothefollowinggroups:

SourcecontroltoolsPackagemanagementtoolsTaskrunnersTestrunnersContinuousintegrationtoolsScaffoldingtools

Page 69: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrerequisitesYouareabouttolearnhowtowriteascript,whichwillautomatemanytasksinyourdevelopmentworkflow;however,beforethat,weneedtoinstallafewtoolsinourdevelopmentenvironment.

Page 70: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Node.jsNode.jsisaplatformbuiltonV8(Google'sopensourceJavaScriptengine).Node.jsallowsustorunJavaScriptoutsideawebbrowser.WecanwritebackendanddesktopapplicationsusingJavaScriptwithNode.js.

Wearenotgoingtowriteserver-sideJavaScriptapplicationsbutwearegoingtoneedNode.jsbecausemanyofthetoolsusedinthischapterareNode.jsapplications.

Ifyoudidn'tinstallNode.jsinthepreviouschapter,youcanvisithttps://nodejs.orgtodownloadtheinstallerforyouroperatingsystem.

Page 71: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AtomAtomisanopensourceeditordevelopedbytheGitHubteam.Theopensourcecommunityaroundthiseditorisreallyactiveandhasdevelopedmanypluginsandthemes.YoucandownloadAtomfromhttps://atom.io/.

Onceyouhavecompletedtheinstallation,opentheeditorandgotothepreferenceswindow.Youshouldbeabletofindasectionwithinthepreferenceswindowtomanagepackagesandanothertomanagethemesjustliketheonesthatwecanseeinthefollowingscreenshot:

Note

TheAtomuserinterfaceisslightlydifferentfromtheotheroperatingsystems.RefertotheAtomdocumentationathttps://atom.io/docsifyouneedadditionalhelptomanagepackagesandthemes.

Weneedtosearchfortheatom-typescriptpackageinthepackagemanagementsectionandinstallit.Wecanadditionallyvisitthethemessectionandinstallathemethatmakesusfeelmorecomfortablewiththeeditor.

Note

WewilluseAtominsteadofVisualStudiobecauseAtomisavailableforLinux,OSX,andWindows,soitwillsuitmostreaders.

Unfortunately,wewillnotcoverVisualStudioCodebecauseitwasannouncedwhenthisbookwasabouttobepublished.VisualStudioCodeisalightweightIDEdevelopedbyMicrosoftandavailableforfreeforWindows,OSX,andLinux.Youcanvisithttps://code.visualstudio.com/ifyouwishtolearnmoreaboutit.

IfyouwanttoworkwithVisualStudio,youwillbeabletofindtheextensiontoenable

Page 72: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypescriptsupportinVisualStudioathttps://visualstudiogallery.msdn.microsoft.com/2d42d8dc-e085-45eb-a30b-3f7d50d55304.

Oneofthehighestratedthemesiscalledseti-uiandisparticularlyusefulbecauseitusesareallygoodsetoficonstohelpustoidentifyeachfileinourapplication.Forexample,thegulpfile.jsorbower.jsonfiles(wewilllearnaboutthesefileslater)arejustJavaScriptandJSONfilesbuttheseti-uithemeisabletoidentifythattheyaretheGulpandBowerconfigurationfilesrespectivelyandwilldisplaytheiriconsaccordingly.

Wecaninstallthisthemebyopeningtheconsoleofouroperatingsystemandrunningthefollowingcommands:

cd~/.atom/packages

gitclonehttps://github.com/jesseweed/seti-ui--depth=1

Note

YouneedtoinstallGittobeabletoruntheprecedingcommand.YouwillfindsomeinformationabouttheGitinstallationlateroninthischapter.

OncewehaveinstalledthethemeandTypeScriptplugin,wewillneedtoclosetheAtomeditorandopenitagaintomakethechangeseffective.Ifeverythinggoeswell,wewillgeta

Page 73: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

confirmationmessageinthetop-rightcorneroftheeditorwindow.

Page 74: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GitandGitHubTowardstheendofthischapter,wewilllearnhowtoconfigureacontinuousintegrationbuildserver.Thebuildserverwillobservechangesinourapplication'scodeandensurethatthechangesdon'tbreaktheapplication.

Inordertobeabletoobservethechangesinthecode,wewillneedtouseasourcecontrolsystem.Thereareafewsourcecontrolsystemsavailable.SomeofthemostwidelyusedonesareSubversion,MercurialandGit.

Sourcecontrolsystemshavemanybenefits.First,theyenablemultipledeveloperstoworkonasourcefilewithoutanyworkbeingoverridden.

Second,sourcecontrolsystemsarealsoagoodwayofkeepingpreviouscopiesofafileorauditingitschanges.Thesefeaturescanbereallyuseful,forexample,whentryingtofindoutwhenanewbugwasintroducedforthefirsttime.

Whileworkingthroughtheexamples,wewillperformsomechangestothesourcecode.WewilluseGitandGitHubtomanagethesechanges.ToinstallGit,gotohttp://git-scm.com/downloadsanddownloadtheexecutableforyouroperatingsystem.Then,gotohttps://github.com/tocreateaGitHubaccount.WhilecreatingtheGitHubaccount,youwillbeofferedafewdifferentsubscriptionplans,thefreeplanofferseverythingweneedtofollowtheexamplesinthischapter.

Page 75: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SourcecontroltoolsNowthatwehaveinstalledGitandcreatedaGitHubaccount,wewilluseGitHubtocreateanewcoderepository.Arepositoryisacentralfilestoragelocation.Itisusedbythesourcecontrolsystemstostoremultipleversionsoffiles.Whilearepositorycanbeconfiguredonalocalmachineforasingleuser,itisoftenstoredonaserver,whichcanbeaccessedbymultipleusers.

Note

GitHuboffersfreesourcecontrolrepositoriesforopensourceprojects.GitHubisreallypopularwithintheopensourcecommunityandmanypopularprojectsarehostedonGitHub(includingTypeScript).However,GitHubisnottheonlyoptionavailableandyoucanusealocalGitrepositoryoranothersourcecontrolserviceprovidersuchasBitbucket.Ifyouwishtolearnmoreaboutthesealternatives,refertotheofficialGitdocumentationathttps://git-scm.com/docortheBitBucketwebsiteathttps://bitbucket.org/.

TocreateanewrepositoryonGitHub,logintoyourGitHubaccountandclickonthelinktocreateanewrepository,whichwecanfindinthetop-rightcornerofthescreen.

Awebformsimilartotheoneinthefollowingscreenshotwillthenappear.Thisformcontainssomefields,whichallowustosettherepository'sname,description,andprivacysettings.

Page 76: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WecanalsoaddaREADME.mdfile,whichusesmarkdownsyntaxandisusedtoaddwhatevertextwewanttotherepository'shomepageonGitHub.Furthermore,wecanaddadefault.gitignorefile,whichisusedtospecifyfilesthatwewouldliketobeignoredbyGitandthereforenotsavedintotherepository.

Lastbutnotleast,wecanalsoselectasoftwarelicensetocoveroursourcecode.Oncewehavecreatedtherepository,wewillnavigatetoourprofilepageonGitHub,findtherepositorythatwehavejustcreated,andgototherepository'spage.Ontherepository'spage,wewillbeabletofindthecloneURLatthebottom-rightcornerofthepage.

Page 77: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Weneedtocopytherepository'scloneURL,openaconsole,andusetheURLasanargumentofthegitclonecommand:

gitclonehttps://github.com/user-name/repository-name.git

Note

SometimestheWindowscommand-lineinterfaceisnotabletofindtheGitandNode.jscommands.

TheeasiestwaytogetaroundthisissueistousetheGitconsole(installedwithGit)ratherthanusingtheWindowscommandline.

IfyouwanttousetheWindowsconsole,youwillneedtomanuallyaddtheGitandNodeinstallationpathstotheWindowsPATHenvironmentvariable.

Also,notethatwewillusetheUNIXpathsyntaxinalltheexamples.

IfyouareworkingwithOSXorLinux,thedefaultcommand-lineinterfaceshouldworkfine.

Thecommandoutputshouldlooksimilartothis:

Cloninginto'repository-name'...

remote:Countingobjects:3,done.

remote:Compressingobjects:100%(3/3),done.

remote:Total3(delta2),reused0(delta0),pack-reused0

Page 78: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Unpackingobjects:100%(3/3),done.

Checkingconnectivity...done.

Wecanthenmoveinsidetherepositorybyusingthechangedirectorycommand(cd)andusethegitstatuscommandtocheckthelocalrepository'sstatus:

cdrepository-name

gitstatus

Onbranchmaster

Yourbranchisup-to-datewith'origin/master'.

nothingtocommit,workingdirectoryclean

Note

WewilluseGitHubthroughoutthisbook.However,ifyouwanttousealocalrepository,youcanusetheGitinitcommandtocreateanemptyrepository.

RefertotheGitdocumentationathttp://git-scm.com/docs/git-inittolearnmoreaboutthegitinitcommandandworkingwithalocalrepository.

Thegitstatuscommandistellingusthattherearenochangesinourworkingdirectory.Let'sopentherepositoryfolderinAtomandcreateanewfilecalledgulpfile.js.Now,runthegitstatuscommandagain,andwewillseethattherearesomenewuntrackedfiles:

Onbranchmaster

Yourbranchisup-to-datewith'origin/master'.

Untrackedfiles:

(use"gitadd<file>..."toincludeinwhatwillbecommitted)

gulpfile.js

nothingaddedtocommitbutuntrackedfilespresent(use"gitadd"totrack)

Note

ThefilesintheAtomprojectexploreraredisplayedusingacolorcode,whichwillhelpustoidentifywhetherafileisnew,orhaschangedsinceweclonedtherepository.

Whenwemakesomechanges,suchasaddinganewfileorchanginganexistingfile,weneedtoexecutethegitaddcommandtoindicatethatwewanttoaddthatchangetoasnapshot:

gitaddgulpfile.js

gitstatus

Onbranchmaster

Yourbranchisup-to-datewith'origin/master'.

Changestobecommitted:

(use"gitresetHEAD<file>..."tounstage)

newfile:gulpfile.js

Page 79: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Nowthatwehavestagedthecontentwewanttosnapshot,wehavetorunthegitcommitcommandtoactuallyrecordthesnapshot.Recordingasnapshotrequiresacommentaryfield,whichcanbeprovidedusingthegitcommitcommandtogetherwithits-margument:

gitcommit-m"addedthenewgulpfile.js"

Ifeverythinghasgonewell,thecommandoutputshouldbesimilartothefollowing:

[master2a62321]addedthenewfilegulpfile.js

1filechanged,1insertions(+)

createmode100644gulpfile.js

Tosharethecommitwithotherdevelopers,weneedtopushourchangestotheremoterepository.Wecandothisbyexecutingthegitpushcommand:

gitpush

ThegitpushcommandwillaskforourGitHubusernameandpasswordandthensendthechangestotheremoterepository.Ifwevisittherepository'spageonGitHub,wewillbeabletofindtherecentlycreatedfile.WewillreturntoGitHublaterinthischaptertoconfigureourcontinuousintegrationserver.

Note

Ifyouareworkinginalargeteam,youmightencountersomefileconflictswhenattemptingtopushsomechangestotheremoterepository.Resolvingafileconflictisoutofthescopeofthisbook;however,ifyouneedfurtherinformationaboutGit,youwillfindanextensiveusermanualathttps://www.kernel.org/pub/software/scm/git/docs/user-manual.html.

Page 80: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PackagemanagementtoolsPackagemanagementtoolsareusedfordependencymanagement,sothatwenolongerhavetomanuallydownloadandmanageourapplication'sdependencies.Wewilllearnhowtoworkwiththreedifferentpackagemanagementtools:Bower,npm,andtsd.

Page 81: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

npmThenpmpackagemanagerwasoriginallydevelopedasthedefaultNode.jspackagemanagementtool,buttodayitisusedbymanytools.Npmusesaconfigurationfile,calledpackage.json,tostorereferencestoallthedependenciesinourapplication.Itisimportanttorememberthatwewillnormallyusenpmtoinstalldependenciesthatwewilluseontheserverside,inadesktopapplication,orwithdevelopmenttools.

Beforeweinstallanypackages,weshouldaddapackage.jsonfiletoourproject.Wecandoitbyexecutingthefollowingcommand:

npminit

Thenpminitcommandwillaskforsomebasicinformationaboutourproject,includingitsname,version,description,entrypoint,testcommand,Gitrepository,keywords,authorandlicense.

Note

Refertotheofficialnpmdocumentationathttps://docs.npmjs.com/files/package.jsonifyouareunsureaboutthepurposesofsomeofthepackage.jsonfieldsmentionedearlier.

Thenpmcommandwillthenshowusapreviewofthepackage.jsonfilethatisabouttobegeneratedandaskforourfinalconfirmation.

Note

RememberthatyouneedtohaveNode.jsinstalledtobeabletousethenpmcommandtool.

Aftercreatingtheproject'spackage.jsonfile,runthenpminstallcommandtoinstallourfirstdependency.Thenpminstallcommandtakesthenameofoneormultipledependenciesseparatedbyasinglespaceasanargumentandasecondargumenttoindicatethescopeoftheinstallation.

Thescopecanbe:

Adependencyatdevelopmenttime(testingframeworks,compilers,andsoon)Adependencyatruntime(awebframework,databaseORMs,andsoon)

Wewillusethegulp-typescriptnpmpackagetocompileourTypeScriptcode;so,let'sinstallitasadevelopmentdependency(usingthe--save-devargument):

npminstallgulp-typescript--save-dev

Toinstallaglobaldependency,wewillusethe-gargument:

npminstalltypescript-g

Page 82: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

Wemightneedadministrativeprivilegestoinstallpackageswithglobalscopeinourdevelopmentenvironment,aswealreadylearnedinthepreviouschapter.

Also,notethatnpmwillnotaddanyentriestoourpackage.jsonfilewheninstallingpackageswithglobalscopebutitisimportantthatwemanuallyaddtherightdependenciestothedevDependenciesandpeerDependenciessectionsinthepackage.jsonfiletoguaranteethatthecontinuousintegrationbuildserverwillresolveallourproject'sdependenciescorrectly.Wewilllearnaboutthecontinuousintegrationbuildserverindetaillaterinthischapter.

Toinstallaruntimedependency,usethe--saveargument:

npminstalljquery--save

Note

JQueryisprobablythemostpopularJavaScriptframeworkorlibraryevercreated.ItisusedtofacilitatetheusageofsomebrowserAPIswithouthavingtoworryaboutsomevendor-specificdifferencesintheAPIs.JQueryalsoprovidesuswithmanyhelpersthatwillhelpusreducetheamountofcodenecessarytoperformtaskssuchasselectinganHTMLnodewithinthetreeofnodesinanHTMLdocument.

ItisassumedthatthereadersofthisbookhaveagoodunderstandingofJQuery.IfyouneedtolearnmoreaboutJQuery,refertotheofficialdocumentationathttps://api.jquery.com/.

Oncewehaveinstalledsomedependenciesinthepackage.jsonfile,thecontentsshouldlooksimilartothis:

{

"name":"repository-name",

"version":"1.0.0",

"description":"example",

"main":"index.html",

"scripts":{

"test":"test"

},

"repository":{

"type":"git",

"url":"https://github.com/username/repository-name.git"

},

"keywords":[

"typescript",

"demo",

"example"

],

"author":"NameSurname",

"contributors":[],

"license":"MIT",

"bugs":{

"url":"https://github.com/username/repository-name/issues"

Page 83: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

},

"homepage":"https://github.com/username/repository-name",

"engines":{},

"dependencies":{

"jquery":"^2.1.4"

},

"devDependencies":{

"gulp-typescript":"^2.8.0"

}

}

Note

Somefieldsinthepackage.jsonfilemustbeconfiguredmanually.Tolearnmoreabouttheavailablepackage.jsonconfigurationfields,visithttps://docs.npmjs.com/files/package.json.

Theversionsofthenpmpackagesusedthroughoutthisbookmayhavebeenupdatedsincethepublicationofthisbook.Refertothepackagesdocumentationathttps://npmjs.comtofindoutpotentialincompatibilitiesandlearnaboutnewfeatures.

Allthenpmpackageswillbesavedunderthenode_modulesdirectory.Weshouldaddthenode_modulesdirectorytoour.gitignorefileasitisrecommendedtoavoidsavingtheapplication'sdependenciesintosourcecontrol.Wecandothisbyopeningthe.gitignorefileandaddinganewlinethatcontainsthenameofthefolder(node_modules).

Thenexttimewecloneourrepository,wewillneedtodownloadallourdependenciesagain,buttodoso,wewillonlyneedtoexecutethenpminstallcommandwithoutanyadditionalparameters:

npminstall

Thepackagemanagerwillthensearchforthepackage.jsonfileandinstallallthedeclareddependencies.

Note

If,inthefuture,weneedtofindannpmpackagename,wewillbeabletousethenpmsearchenginesathttps://www.npmjs.cominordertofindit.

Page 84: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BowerBowerisanotherpackagemanagementtool.Itisreallysimilartonpmbutitwasdesignedspecificallytomanagefrontenddependencies.Asaresult,manyofthepackagesareoptimizedforitsusageinawebbrowser.

WecaninstallBowerbyusingnpm:

npminstall-gbower

Insteadofthepackage.jsonfile,Bowerusesaconfigurationfilenamedbower.json.WecanusethemajorityofthenpmcommandsandargumentsinBower.Forexample,wecanusethebowerinitcommandtocreatetheinitialbowerconfigurationfile:

bowerinit

Note

Theinitialconfigurationfileisquitesimilartothepackage.jsonfile.Refertotheofficialdocumentationathttp://bower.io/docs/config/ifyouwanttolearnmoreaboutthebower.jsonconfigurationfields.

Wecanalsousethebowerinstallcommandtoinstallapackage:

bowerinstalljquery

Furthermore,wecanalsousetheinstallscopearguments:

bowerinstalljquery--save

bowerinstalljasmine--save-dev

AlltheBowerpackageswillbesavedunderthebower_componentsdirectory.Asyouhavealreadylearned,itisrecommendedtoavoidsavingyourapplication'sdependenciesinyourremoterepository,soyoushouldalsoaddthebower_componentsdirectorytoyour.gitignorefile.

Page 85: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

tsdInthepreviouschapter,welearnedthatTypeScriptbydefaultincludesafilelib.d.tsthatprovidesinterfacedeclarationsforthebuilt-inJavaScriptobjectsaswellastheDocumentObjectModel(DOM)andBrowserObjectModel(BOM)APIs.TheTypeScriptfileswiththeextension.d.tsareaspecialkindofTypeScriptfileknownastypedefinitionfilesordeclarationfiles.

Thetypedefinitionfilesusuallycontainthetypedeclarationsofthird-partylibraries.ThesefilesfacilitatetheintegrationbetweentheexistingJavaScriptlibrariesandTypeScript.If,forexample,wetrytoinvoketheJQueryinaTypeScriptfile,wewillgetanerror:

$.ajax({/**/});//cannotfindname'$'

Toresolvethisissue,weneedtoaddareferencetotheJQuerytypedefinitionfileinourTypeScriptcode,asshowninthefollowinglineofcode:

///<referencepath="jquery.d.ts">

Fortunately,wedon'tneedtocreatethetypedefinitionfilesbecausethereisanopensourceprojectknownasDefinitelyTypedthatalreadycontainstypedefinitionfilesformanyJavaScriptlibraries.IntheearlydaysofTypeScriptdevelopment,developershadtomanuallydownloadandinstallthetypedefinitionfilesfromtheDefinitelyTypedprojectwebsite,butthosedaysaregone,andtodaywecanuseamuchbettersolutionknownastsd.

ThetsdacronymstandsforTypeScriptDefinitionsanditisapackagemanagerthatwillhelpustomanagethetypedefinitionfilesrequiredbyourTypeScriptapplication.Justlikenpmandbower,tsdusesaconfigurationfilenamedtsd.jsonandstoresallthedownloadedpackagesunderadirectorynamedtypings.

Runthefollowingcommandtoinstalltsd:

npminstalltsd-g

Wecanusethetsdinitcommandtogeneratetheinitialtsd.jsonfileandthetsdinstallcommandtodownloadandinstalldependencies:

tsdinit//generatetsd.json

tsdinstalljquery--save//installjquerytypedefinitions

YoucanvisittheDefinitelyTypedprojectwebsiteathttps://github.com/borisyankov/DefinitelyTypedtosearchfortsdpackages.

Page 86: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TaskrunnersAtaskrunnerisatoolusedtoautomatetasksinthedevelopmentprocess.ThetaskcanbeusedtoperformawidevarietyofoperationssuchasthecompilationofTypeScriptfilesorthecompressionofJavaScriptfiles.ThetwomostpopularJavaScripttaskrunnersthesedaysareGruntandGulp.

Gruntstartedtobecomepopularinearly2012andsincethentheopensourcecommunityhasdevelopedalargenumberofGrunt-compatibleplugins.

Ontheotherhand,Gulpstartedtobecomepopularinlate2013;therefore,therearelesspluginsavailableforGulp,butitisquicklycatchingupwithGrunt.

Besidesthenumberofpluginsavailable,themaindifferencebetweenGulpandGruntisthatwhileinGruntwewillworkusingfilesastheinputandoutputofourtasks,inGulpwewillworkwithstreamsandpipesinstead.Gruntisconfiguredusingsomeconfigurationfieldsandvalues.However,Gulppreferscodeoverconfiguration.ThisapproachmakestheGulpconfigurationsomehowmoreminimalisticandeasiertoread.

Note

Inthisbook,wewillworkwithGulp;however,ifyouwanttolearnmoreaboutGrunt,youcandosoathttp://gruntjs.com/.

InordertogainagoodunderstandingofGulp,wecanusetheprojectthatwehavealreadycreatedandaddsomeextrafoldersandfilestoit.Alternatively,wecanstartanewprojectfromscratch.Wewillconfiguresometasks,whichwillreferencepaths,folders,andfilesnumeroustimes,sothefollowingdirectorytreestructureshouldhelpusunderstandeachofthesetasks:

├──LICENSE

├──README.md

├──index.html

├──gulpfile.js

├──karma.conf.js

├──tsd.json

├──package.json

├──bower.json

├──source

│└──ts

│└──*.ts

├──test

│└──main.test.ts

├──data

│└──*.json

├──node_modules

│└──...

├──bower_components

│└──...

Page 87: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

└──typings

└──...

Note

Acopyofafinishedexampleprojectisprovidedinthecompanionsourcecode.Thecodeisprovidedtohelpyoufollowthecontent.Youcanusethefinishedprojecttohelpimprovetheunderstandingoftheconceptsdiscussedintherestofthischapter.

Let'sstartbyinstallinggulpgloballywithnpm:

npminstall-ggulp

Theninstallgulpinourpackage.jsondevDependencies:

npminstall--save-devgulp

CreateaJavaScriptfilenamedgulpfile.jsinsidetherootfolderofourproject,whichshouldcontainthefollowingpieceofcode:

vargulp=require('gulp');

gulp.task('default',function(){

console.log('HelloGulp!');

});

And,finally,rungulp(wemustexecutethiscommandfromwherethegulpfile.jsfileislocated):

gulp

WehavecreatedourfirstGulptask,whichisnameddefault.Whenwerunthegulpcommand,itwillautomaticallytrytosearchforthegulpfile.jsfileinthecurrentdirectory,andoncefound,itwilltrytofindthedefaulttask.

Page 88: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CheckingthequalityoftheTypeScriptcodeThedefaulttaskisnotperforminganyoperationsintheprecedingexample,butwewillnormallyuseaGulppluginineachtask.Wewillnowaddasecondtask,whichwillusethegulp-tslintplugintocheckwhetherourTypeScriptcodefollowsaseriesofrecommendedpractices.

Weneedtoinstallthepluginwithnpm:

npminstallgulp-tslint--save-dev

Wecanthenloadthepluginintoourgulpfile.jsfileandaddanewtask:

vartslint=require('gulp-tslint');

gulp.task('lint',function(){

returngulp.src([

'./source/ts/**/**.ts','./test/**/**.test.ts'

]).pipe(tslint())

.pipe(tslint.report('verbose'));

});

Wehavenamedthenewtasklint.Let'stakealookattheoperationsperformedbythelinttask,stepbystep:

1. Thegulpsrcfunctionwillfetchthefilesinthedirectorylocatedat./source/tsanditssubdirectorieswiththefileextension.ts.Wewillalsofetchallthefilesinthedirectorylocatedat./testanditssubdirectorieswiththefileextension.test.ts.

2. Theoutputstreamofthesrcfunctionwillbethenredirectedusingthepipefunctiontobeusedasthetslintfunctioninput.

3. Finally,wewillusetheoutputofthetslintfunctionastheinputofthetslint.reportfunction.

Nowthatwehaveaddedthelinttask,wewillmodifythegulpfile.jsfiletoindicatethatwewanttorunlintasasubtaskofthedefaulttask:

gulp.task('default',['lint']);

Note

Manypluginsallowustoindicatethatsomefilesshouldbeignoredbyaddingtheexclamationsymbol(!)beforeapath.Forexample,thepath!path/*.d.tswillignoreallfileswiththeextension.d.ts;thisisusefulwhenthedeclarationfilesandsourcecodefilesarelocatedinthesamedirectory.

Page 89: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CompilingtheTypeScriptcodeWewillnowaddtwonewtaskstocompileourTypeScriptcode(onefortheapplication'slogicandonefortheapplication'sunittests).

Wewillusethegulp-typescriptplugin,soremembertoinstallitasadevelopmentdependencyusingthenpmpackagemanager,justaswedidpreviouslyinthischapter:

npminstall-ggulp-typescript

Wecanthencreateanewgulp-typescriptprojectobject:

varts=require('gulp-typescript');

vartsProject=ts.createProject({

removeComments:true,

noImplicitAny:true,

target:'ES3',

module:'commonjs',

declarationFiles:false

});

Note

Ithasbeenannouncedthatthegulp-typescriptpluginwillsoonsupporttheusageofaspecialJSONfilenamedtsconfig.json.ThisfileisusedtostoretheTypeScriptcompilerconfiguration.Whenthefileisavailable,itisusedbythecompilerduringthecompilationprocess.

Thetsconfig.jsonfileisusefulbecauseitpreventsusfromhavingtowriteallthedesiredcompilerparameterswhenusingitsconsoleinterface.Refertothegulp-typescriptdocumentation,whichcanbefoundathttps://www.npmjs.com/package/gulp-typescript,tolearnmoreaboutthisfeature.

Intheprecedingcodesnippet,wehaveloadedtheTypeScriptcompilerasadependencyandthencreatedanobjectnamedtsProject,whichcontainsthesettingstobeusedbytheTypeScriptcompilerduringthecompilationofourcode.Wearenowreadytocompileourapplication'ssourcecode:

gulp.task('tsc',function(){

returngulp.src('./source/ts/**/**.ts')

.pipe(ts(tsProject))

.js.pipe(gulp.dest('./temp/source/js'));

});

Thetsctaskwillfetchallthe.tsfilesinthedirectorylocatedat./source/tsanditssubdirectoriesandpassthemasastreamtotheTypeScriptcompiler.ThecompilerwillusethecompilationsettingspassedasthetsProjectargumentandthensavetheoutputJavaScriptfilesintothepath./temp/sources/js.

Page 90: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WealsoneedtocompilesomeunittestswritteninTypeScript.ThetestsarelocatedinthetestfolderandwewanttheoutputJavaScriptfilestobestoredundertemp/test.Usingthesameprojectconfigurationobjectinadifferenttaskandwithdifferentinputfilescanresultinbadperformanceandunexpectedbehavior;soweneedtoinitializeanothergulp-typescriptprojectobject.ThistimewewillnametheobjecttsTestProject:

vartsTestProject=ts.createProject({

removeComments:true,

noImplicitAny:true,

target:'ES3',

module:'commonjs',

declarationFiles:false

});

Thetsc-testtaskisalmostidenticaltothetsctask,butinsteadofcompilingtheapplication'scode,itwillcompiletheapplication'stests.Sincethesourceandtestarelocatedindifferentdirectories,wehaveuseddifferentpathsinthistask:

gulp.task('tsc-tests',function(){

returngulp.src('./test/**/**.test.ts')

.pipe(ts(tsTestProject))

.js.pipe(gulp.dest('./temp/test/'));

});

Wewillupdatethedefaulttaskoncemoreinordertoperformthenewtasks:

gulp.task('default',['lint','tsc','tsc-tests']);

Page 91: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

OptimizingaTypeScriptapplicationWhenwecompileourTypescriptcode,thecompilerwillgenerateaJavaScriptfileforeachcompiledTypeScriptfile.Ifweruntheapplicationinawebbrowser,thesefileswon'treallybeusefulontheirownbecausetheonlywaytousethemwouldbetocreateanindividualHTMLscripttagforeachoneofthem.

Alternatively,wecouldfollowtwodifferentapproaches:

Wecoulduseatool,suchastheRequireJSlibrary,toloadeachofthosefilesondemandusingAJAX.Thisapproachisknownasasynchronousmoduleloading.Tofollowthisapproach,wewillneedtochangetheconfigurationoftheTypeScriptcompilertousetheasynchronousmoduledefinition(AMD)notation.WecouldconfiguretheTypeScriptcompilertousetheCommonJSmodulenotationanduseatool,suchasBrowserify,totracetheapplication'smodulesanddependenciesandgenerateahighlyoptimizedsinglefile,whichwillcontainalltheapplication'smodules.

Inthisbook,wewillusetheCommonJSmethodbecauseitishighlyintegratedwithBrowserifyandGulp.

Note

IfyouhaveneverworkedwithAMDorCommonJSmodulesbefore,don'tworrytoomuchaboutitfornow.WewillfocusonmodulesinChapter4,Object-OrientedProgrammingwithTypeScript.

Wecanfindtheapplication'srootmodule(namedmain.tsinourexample)inthecompanioncode.Thisfilecontainsthefollowingcode:

///<referencepath="./references.d.ts"/>

import{headerView}from'./header_view';

import{footerView}from'./footer_view';

import{loadingView}from'./loading_view';

headerView.render();

footerView.render();

loadingView.render();

Tip

Theprecedingimportstatementsareusedtoaccessthecontentsofsomeexternalmodules.WewilllearnmoreaboutexternalmodulesinChapter4,Object-OrientedProgrammingwithTypeScript.

Whencompiled(usingtheCommonJSmodulenotation),theoutputJavaScriptcodewilllooklikethis:

varheaderView=require('./header_view');

Page 92: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

varfooterView=require('./footer_view');

varloadingView=require('./loading_view');

headerView.render();

footerView.render();

loadingView.render();

Aswecanseeinthefirstthreelines,themain.jsfiledependsontheotherthreeJavaScriptfiles:header_view.js,footer_view.js,andloading_view.js.Ifwecheckthecompanioncode,wewillseethatthesefilesalsohavesomedependencies.

Wewillnormallyrefertothesedependenciesasmodules.Importingamoduleallowsustousethepublicparts(alsoknownastheexportedparts)ofamodulefromanothermodule.

Browserifyisabletotracethefulltreeofdependenciesandgenerateahighlyoptimizedsinglefile,whichwillcontainalltheapplication'smodulesanddependencies.

Wewillnowaddtwonewtaskstoourautomatedbuild(gulpfile.js).Inthefirstone,wewillconfigureBrowserifytotracethedependenciesofourapplication'smodules.Inthesecondone,wewillconfigureBrowserifytotracethedependenciesofourapplication'sunittests.

Weneedtoinstallsomepackagesbeforeimplementingthenewtask:

npminstallbrowserifyvinyl-transformgulp-uglifygulp-sourcemaps

Wecanthenimportthemodulesandwritesomeinitializationcode:

Varbrowserify=require('browserify'),

transform=require('vinyl-transform'),

uglify=require('gulp-uglify'),

sourcemaps=require('gulp-sourcemaps');

varbrowserified=transform(function(filename){

varb=browserify({entries:filename,debug:true});

returnb.bundle();

});

Intheprecedingcodesnippet,wehaveloadedtherequiredpluginsanddeclaredafunctionnamedbrowserified,whichisrequiredforcompatibilityreasons.ThebrowserifiedfunctionwilltransformaregularNode.jsstreamintoaGulp(bufferedvinyl)stream.

Let'sproceedtoimplementtheactualtask:

gulp.task('bundle-js',function(){

returngulp.src('./temp/source/js/main.js')

.pipe(browserified)

.pipe(sourcemaps.init({loadMaps:true}))

.pipe(uglify())

.pipe(sourcemaps.write('./'))

.pipe(gulp.dest('./dist/source/js/'));

});

Page 93: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thetaskwejustdefinedwilltakethefilemain.jsastheentrypointofourapplicationandtracealltheapplication'smodulesanddependenciesfromthispoint.ItwillthengenerateonesinglestreamcontainingahighlyoptimizedJavaScript.

Wewillthenusetheuglifyplugintominimizetheoutputsize.Thereducedfilesizewillreducetheapplication'sloadingtime,butwillmakeithardertodebug.Wewillalsogenerateasourcemapfiletofacilitatethedebuggingprocess.

Note

Uglifyremovesalllinebreaksandwhitespacesandreducesthelengthofsomevariablenames.Thesourcemapfilesallowustomapthereducedfiletoitsoriginalcodewhiledebugging.

Asourcemapprovidesawayofmappingcodewithinacompressedfilebacktoitsoriginalpositioninasourcefile.Thismeanswecaneasilydebuganapplicationevenafteritsassetshavebeenoptimized.TheChromeandFirefoxdevelopertoolsbothshipwithbuilt-insupportforsourcemaps.

Thebundle-testtaskisreallysimilartotheprevioustask.Thistime,wewillavoidusinguglifyandsourcemapsbecauseusuallywewon'tneedtooptimizethedownloadtimesofourunittests.Asyoucansee,wedon'thaveasingleentrypointbecausewewillallowtheexistenceofmultipleentrypoints(eachentrypointwillbelikedtoacollectionofautomatedtestsknownastestsuite.Don'tworryifyouarenotfamiliarwiththisterm,aswewilllearnmoreaboutitinChapter7,ApplicationTesting):

gulp.task('bundle-test',function(){

returngulp.src('./temp/test/**/**.test.js')

.pipe(browserified)

.pipe(gulp.dest('./dist/test/'));

});

Finally,wehavetoupdatethedefaulttasktoalsoperformthenewtasks:

gulp.task('default',['lint','tsc','tsc-tests','bundle-js','bundle-test']);

Note

WehavecreatedatasktocompiletheTypeScriptfilesintoJavaScriptfiles.TheJavaScriptfilesarestoredinatemporaryfolderandasecondtaskbundlesalltheJavaScriptfilesintoasinglefile.Inarealcorporateenvironment,itisnotrecommendedtostorefilestemporarilywhenworkingwithGulp.Wecanperformalltheseoperationswithonesingletaskbypassingtheoutputstreamofanoperationastheinputofthefollowingoperation.However,inthisbook,wewilltrytosplitthetaskstofacilitatetheunderstandingofeachtask.

Ifwetrytoexecutethedefaulttaskafteraddingthesechanges,wewillprobablyexperiencesomeissuesbecausethetasksareexecutedinparallelbydefault.Wewillnowlearnhowtocontrolthetask'sexecutionordertoavoidthiskindofissue.

Page 94: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ManagingtheGulptasks'executionorderSometimeswewillneedtorunourtasksinacertainorder(forexample,weneedtocompileourTypeScriptintoJavaScriptbeforewecanexecuteourunittests).Controllingthetasks'executionordercanbechallengingsinceinGulpallthetasksareasynchronousbydefault.

Therearethreewaystomakeatasksynchronous:

PassinginacallbackReturningastreamReturningapromise

Tip

RefertoChapter3,WorkingwithFunctionstolearnmoreabouttheusagecallbacksandpromises.

Let'stakealookatthefirsttwoways(wewillnotcovertheusageofpromisesinthischapter):

//Passingacallback(cb)

gulp.task('sync',function(cb){//notethecbargument

//setTimeoutcouldbeanyasynctask

setTimeout(function(){

cb();//notethecbusagehere

},1000);

});

//Returningastream

gulp.task('sync',function(){

returngulp.src('js/*.js')//notethereturnkeywordhere

.pipe(concat('script.min.js')

.pipe(uglify())

.pipe(gulp.dest('../dist/js');

});

Nowthatwehaveasynchronoustask,wecancombineitwiththetaskdependencynotationtomanagetheexecutionorder:

gulp.task('secondTask',['sync'],function(){

//thistaskwillnotstartuntil

//thesynctaskisalldone!

});

Intheprecedingcodesnippet,thesecondTasktaskwillnotstartuntilthesynctaskisdone.Now,let'simaginethatthereisathirdtasknamedthirdTask.WewillwritethefollowingcodesnippethopingthatitwillexecutethesynctaskbeforethethirdTasktaskandfinallythedefaulttask,butitwillinfactrunthesynctaskandthirdTasktaskinparallel:

gulp.task('default',['sync','thirdTask'],function(){

//dostuff

Page 95: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

});

Fortunately,wecaninstalltherun-sequenceGulppluginvianpm,whichwillallowustohavebettercontroloverthetaskexecutionorder:

varrunSequence=require('run-sequence');

gulp.task('default',function(cb){

runSequence(

'lint',//lint

['tsc','tsc-tests'],//compile

['bundle-js','bundle-test'],//optimize

'karma'//test

'browser-sync',//serve

cb//callback

);

});

Theprecedingcodesnippetwillruninthefollowingorder:

1. lint.2. tscandtsc-testsinparallel.3. bundle-jsandbundle-testinparallel.4. karma.5. browser-sync.

Note

TheGulpdevelopmentteamannouncedplanstoimprovethemanagementofthetaskexecutionorderwithouttheneedforexternalpluginswhenthisbookwasabouttobepublished.RefertotheGulpdocumentationandreleasenotesonfuturereleasestolearnmoreaboutit.Thedocumentationcanbefoundathttps://github.com/gulpjs/gulp/blob/master/docs/README.md.

Page 96: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestrunnersAtestrunnerisatoolthatallowsustoautomatetheexecutionofourapplication'sunittests.

Note

Unittestingreferstothepracticeoftestingcertainfunctionsandareas(units)ofourcode.Thisgivesustheabilitytoverifythatourfunctionsworkasexpected.Itisassumedthatthereaderhassomeunderstandingoftheunittestprocess,butthetopicsexploredherewillbecoveredinamuchhigherlevelofdetailinChapter7,ApplicationTesting.

Wecanuseatestrunnertoautomaticallyexecuteourapplication'stestsuitesinmultiplebrowsersinsteadofhavingtomanuallyopeneachwebbrowserinordertoexecutethetests.

WewilluseatestrunnerknownasKarma.Karmaiscompatiblewithmultipleunittestingframeworks,butwewillusetheMochatestingframeworktogetherwithtwolibraries:Chai(anassertionlibrary)andSinon(amockingframework).

Note

Youdon'tneedtoworrytoomuchabouttheselibrariesrightnowbecausewewillfocusontheirusageinChapter7,ApplicationTesting.

Let'sstartbyusingnpmtoinstallthetestingframeworkthatwearegoingtouse:

npminstallmochachaisinon--save-dev

Wewillcontinuebyinstallingthekarmatestrunnerandsomedependencies:

npminstallkarmakarma-mochakarma-chaikarma-sinonkarma-coveragekarma-

phantomjs-launchergulp-karma--save-dev

Afterinstallingallthenecessarypackages,wehavetoaddanewGulptasktothegulpfile.jsfile.Thenewtaskwillruntheapplication'sunittestsusingKarma:

Varkarma=require("gulp-karma");

gulp.task('karma',function(cb){

gulp.src('./dist/test/**/**.test.js')

.pipe(karma({

configFile:'karma.conf.js',

action:'run'

}))

.on('end',cb)

.on('error',function(err){

//Makesurefailedtestscausegulptoexitnon-zero

throwerr;

});

});

Page 97: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Intheprecedingcodesnippet,wearefetchingallthefileswiththeextension.test.jsunderthedirectorylocatedat./dist/test/andallitssubdirectories.WewillthenpassthefilestotheKarmaplugintogetherwiththelocationofthekarma.conf.jsfile,whichcontainstheKarmaconfiguration.WewillcreateanewJavaScriptfilenamedkarma.conf.jsintheproject'srootdirectoryandcopythefollowingcodeintoit:

module.exports=function(config){

'usestrict';

config.set({

basePath:'',

frameworks:['mocha','chai','sinon'],

browsers:['PhantomJS'],

reporters:['progress','coverage'],

plugins:[

'karma-coverage',

'karma-mocha',

'karma-chai',

'karma-sinon',

'karma-phantomjs-launcher'

],

preprocessors:{

'./dist/test/*.test.js':['coverage']

},

port:9876,

colors:true,

autoWatch:false,

singleRun:false,

logLevel:config.LOG_INFO

});

};

TheconfigurationfiletellsKarmaabouttheapplication'sbasepath,frameworks(Mocha,Chai,andSinon.JS),browsers(PhantomJS),plugins,andreportersthatwewanttouseduringthetests'execution.PhantomJSisaheadlesswebbrowser,itisusefulbecauseitcanexecutetheunittestwithoutactuallyhavingtoopenawebbrowser.

Note

WeshouldrunthetestsinrealwebbrowsersalongwithPhantomJSbeforedoingaproductiondeployment.ThereareKarmaplugins,suchaskarma-firefox-launcherandkarma-chrome-launcher,whichwillallowustoruntheunittestsinthebrowsersofourchoice.

Karmausestheprogressreporterbydefaulttoletusknowthestatusofthetestexecutionprocess.Weaddedthecoveragereporteraswellbecausewewanttohaveanideaofwhatpercentageofourapplication'scodehasbeentestedwithunittests.Afteraddingthecoveragereporterandrunningourunittestswewillbeabletofindthecoveragereportunderafoldernamedcoverage,whichshouldbelocatedinthesamedirectorywherethekarma.conf.jsfilewaslocated.

Page 98: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IfwelookattheKarmaconfigurationdocumentationathttp://karma-runner.github.io/0.8/config/configuration-file.html,wewillnoticethatwearemissingthefilesfieldinourkarma.conf.jsfile.Wedidn'tindicatethelocationofourunittestsbecausetheGulptaskwillpassthestream,whichcontainstheunittests',filestoKarma,andthentheKarmataskisexecuted.

Page 99: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Synchronizedcross-devicetestingWewilladdonelasttasktothegulpfile.jsfile,whichwillallowustorunourapplicationinawebbrowser.Weneedtoinstallthebrowser-syncpackagebyusingnpm:

npminstall-gbrowser-sync

Wewillthencreatetwonewtasks.Thesetasksarejustusedtogroupafewtasksintoonemaintask.WearedoingthisbecausesometimeswewanttorefreshawebpagetoseetheeffectofchangingsomeTypeScriptcodeandweneedtorunanumberoftasks(compilation,bundling,andsoon)beforewecanactuallyseethechangesinawebbrowser.Bygroupingallthesetasksintohigher-leveltasks,wecansavesometimeandmakeourconfigurationfilesmorereadable:

gulp.task('bundle',function(cb){

runSequence('build',[

'bundle-js','bundle-test'

],cb);

});

gulp.task('test',function(cb){

runSequence('bundle',['karma'],cb);

});

Theprecedingtwotasksareusedtogroupallthebuild-relatedtasksintoahigher-leveltask(namedbundle)andtogroupallthetest-relatedtasksintoahigher-leveltask(namedtest).

Afterinstallingthepackageandimplementingtheprecedingtwotasks,wecanaddanewGulptasktothegulpfile.jsfile:

varbrowserSync=require('browser-sync');

gulp.task('browser-sync',['test'],function(){

browserSync({

server:{

baseDir:"./dist"

}

});

returngulp.watch([

"./dist/source/js/**/*.js",

"./dist/source/css/**.css",

"./dist/test/**/**.test.js",

"./dist/data/**/**",

"./index.html"

],[browserSync.reload]);

});

Inthistask,weareconfiguringBrowserSynctohostinthelocalwebserverallthestaticfilesunderthedistdirectory.Wethenusethegulpwatchfunctiontoindicatethat,ifthecontentofanyofthefilesunderthedistdirectorychanges,BrowserSyncshouldautomaticallyrefresh

Page 100: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ourwebbrowser.

Whensomechangesaredetected,thetesttaskisinvoked.Becausethetesttaskinvokesthebundletasks,anychangeswilltriggertheentireprocess(buildandtest)beforerefreshingthewebpageanddisplayingthenewfilesinawebbrowser.

BrowserSyncisareallypowerfultool,itallowsustotestinonedeviceandautomaticallyrepeatouractions(clicks,scrolls,andsoon)onasmanydevicesaswewant.Itwillalsoallowustodebugourapplicationsremotely,whichcanbereallyusefulwhenwearetestinganapplicationonmobiledevices.

Synchronizingdevicesisreallysimple.Ifwerunthebrowser-synctask,theapplicationwillbelaunchedinthedefaultwebbrowser.Ifwelookattheconsoleoutput,wewillseethattheapplicationisrunninginoneURL(http://localhost:3000)andtheBrowserSynctoolsareavailableinasecondURL(http://localhost:3001):

[BS]AccessURLs:

---------------------------------------

Local:http://localhost:3000

External:http://192.168.241.17:3000

---------------------------------------

UI:http://localhost:3001

UIExternal:http://192.168.241.17:3001

---------------------------------------

[BS]Servingfilesfrom:./dist

IfweopenanothertabinourbrowserpointingtotheBrowserSynctoolsURL(http://localhost:3001,intheexample),wewillaccesstheBrowserSynctoolsuserinterface:

Page 101: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WecanusetheBrowserSynctoolsuserinterfacetoaccesstheremotedebuggingoptionsanddevicesynchronizationoptions.Tosynchronizeanewdevice,wejustneedtouseaphoneortabletconnectedtothesamelocalareanetworkandopentheindicatedexternalURLinthedevice'swebbrowser.

IfyouwishtolearnmoreaboutBrowserSync,visittheofficialprojectdocumentationathttp://www.browsersync.io/docs/.

Page 102: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ContinuousIntegrationtoolsContinuousIntegration(CI)isadevelopmentpracticethathelpstopreventpotentialintegrationissues.Softwareintegrationissuesreferstothedifficultiesthatmayariseduringthepracticeofcombiningindividuallytestedsoftwarecomponentsintoanintegratedwhole.Softwareisintegratedwhencomponentsarecombinedintosubsystemsorwhensubsystemsarecombinedintoproducts.

Componentsmaybeintegratedafterallofthemareimplementedandtested,asinawaterfallmodelorabigbangapproach.Ontheotherhand,CIrequiresdeveloperstocommittheircodedailyintoaremotecoderepository.Eachcommitisthenverifiedbyanautomatedbuild,allowingteamstodetectintegrationissuesearlier.

Inthischapter,wehavecreatedaremotecoderepositoryandanautomatedbuild,butwehaven'tconfiguredatooltoobserveourcommitsandruntheautomatebuildaccordingly.WeneedaCIserver.TherearemanyoptionswhenitcomestochoosingaCIserver,butexploringtheseoptionsisoutofthescopeofthisbook.WewillworkwithTravisCIbecauseitishighlyintegratedwithGitHubandisfreeforopensourceprojectsandlearningpurposes.

ToconfigureTravisCI,weneedtovisitthewebsitehttps://travis-ci.organdloginusingourGitHubcredentials.Oncewehaveloggedin,wewillbeabletoseealistofourpublicGitHubrepositoriesandwillalsobeabletoenabletheCI.

Tofinishtheconfiguration,weneedtoaddafilenamedtravis.ymltoourapplication'srootdirectory,whichcontainstheTravisCIconfiguration:

language:node_js

node_js:

-"0.10""

Note

TherearemanyotheravailableTravisCIconfigurationoptions.Refertohttp://docs.travis-ci.com/tolearnmoreabouttheavailableoptions.

Aftercompletingthesetwosmallconfigurationsteps,TravisCIwillbereadytoobservethecommitstoourremotecoderepository.

Note

Ifthebuildworksinthelocaldevelopmentenvironment,butfailsintheCIserver,wewill

Page 103: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

havetocheckthebuilderrorlogandtrytofigureoutwhatwentwrong.ChancesarethatthesoftwareversionsinourenvironmentwillbeaheadoftheonesintheCIserverandwewillneedtoindicatetoTravisCIthatadependencyneedstobeinstalledorupdated.WecanfindtheTravisCIdocumentationathttp://docs.travis-ci.com/user/build-configuration/tolearnhowtoresolvethiskindofissue.

Page 104: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ScaffoldingtoolsAscaffoldingtoolisusedtoautogeneratetheprojectstructure,buildscripts,andmuchmore.ThemostpopularscaffoldingtoolthesedaysisYeoman.Yeomanusesaninternalcommandknownasyo,apackagemanager,andataskrunnerofourchoicetogenerateprojectsbasedontemplates.

Theprojecttemplatesareknownasgeneratorsandtheopensourcecommunityhasalreadypublishedmanyofthem,soweshouldbeabletofindonethatmoreorlesssuitsourneeds.Alternatively,wecanwriteandpublishourownYeomangenerator.

WewillnowcreateanewprojecttoshowcasehowYeomancanhelpustosavesometime.Yeomanwillgeneratethepackage.jsonandbower.jsonfilesandautomaticallyinstallsomedependenciesforus.

Theyocommandcanbeinstalledusingnpm:

npminstall-gyo

Afterinstallingtheyocommand,wewillneedtoinstallatleastonegenerator.Weneedtofindageneratorforthekindofprojectthatwewishtocreate.

WearegoingcreateanewprojectusingGulpasthetaskrunnerandTypeScripttoshowcasetheusageofYeoman.Wecanuseageneratorcalledgenerator-typescript.Thelistofavailablegeneratorscanbefoundonlineathttp://yeoman.io/generators/.

Wecaninstallageneratorbyusingnpm:

npminstall-ggenerator-typescript

Afterinstallingthegenerator,wecanuseitwiththehelpoftheyocommand:

yotypescript

If,forexample,wealsowantedtouseSass,wecouldusethegenerator-gulp-sass-typescriptgeneratorinstead:

npminstall-ggenerator-gulp-sass-typescript

Someofthegeneratorsareinteractiveandwillallowustoselectwhetherwewanttoaddsomeoptionalthird-partylibrariestotheprojectornot.Let'srunthegeneratortoseewhatitlookslike:

yogenerator-gulp-sass-typescript

Thescreenthatisdisplayedcontainsaseriesofstepstoguideusthroughtheprocessofcreatinganewproject,whichincludesGulpasthetaskrunner,SassastheCSSpreprocessor,

Page 105: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

andTypeScriptastheprogramminglanguage:

Afterexecutingthegenerator,theprojecttemplatewillgenerateadirectorytreesimilartothefollowingone:

├──app

│├──index.html

│├──sass

││└──styles.scss

│├──scripts

││└──main.js

│├──styles

││└──styles.css

│└──ts

│└──main.ts

├──bower.json

├──bower_components

│└──...

├──gulpfile.js

├──node_modules

│└──...

Page 106: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

└──package.json

Thebower.json,package.json,andgulpfile.jsfiles(theGulptaskrunnerconfiguration)areautogeneratedandwillsaveusaconsiderableamountoftime.

Note

Itisneveragoodideatoletatoolgeneratesomecodeforusifwedon'treallyunderstandwhatthatcodedoes.WhileinthefutureyoushoulddefinitelyconsiderusingYeomantogenerateanewproject,itisrecommendedtogainagoodunderstandingoftaskandtestrunnersbeforeusingascaffoldingtool.

Page 107: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,youlearnedhowtoworkwithasourcecontrolrepositoryandhowtouseGulptomanagethetasksinanautomatedbuild.TheautomatedbuildhelpsustovalidatethequalityoftheTypeScriptcode,compileit,testit,andoptimizeit.Youalsolearnedhowtoinstallthird-partypackagesandTypeScripttypedefinitionsforthosethird-partycomponents.

Towardstheendofthechapter,youlearnedhowtousetheautomatedbuildandacontinuousintegrationservertoreducetheimpactofpotentialintegrationissues.

Inthenextchapter,youwilllearnaboutfunctions.

Page 108: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter3.WorkingwithFunctionsInChapter1,IntroducingTypeScript,wetookafirstlookattheusageoffunctions.FunctionsarethefundamentalbuildingblockofanyapplicationinTypeScript,andtheyarepowerfulenoughtodeservethededicationofanentirechaptertoexploretheirpotential.

Inthischapter,wewilllearntoworkwithfunctionsindepth.Thechapterisdividedintotwomainsections.Inthefirstsection,wewillstartwithaquickrecapofsomebasicconceptsandthenmoveontosomelesscommonlyknownfunctionfeaturesandusecases.Thefirstsectionincludesthefollowingconcepts:

FunctiondeclarationandfunctionexpressionsFunctiontypesFunctionswithoptionalparametersFunctionswithdefaultparametersFunctionswithrestparametersFunctionoverloadingSpecializedoverloadingsignatureFunctionscopeImmediatelyinvokedfunctionsGenericsTagfunctionsandtaggedtemplates

ThesecondsectionfocusesonTypeScriptasynchronousprogrammingcapabilitiesandincludesthefollowingconcepts:

CallbacksandhigherorderfunctionsArrowfunctionsCallbackhellPromisesGeneratorsAsynchronousfunctions(asyncandawait)

Page 109: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WorkingwithfunctionsinTypeScriptInthissection,wewillfocusonthedeclarationandusageoffunctions,parameters,andarguments.WewillalsointroduceoneofthemostpowerfulfeaturesofTypeScript:Generics.

Page 110: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctiondeclarationsandfunctionexpressionsInthefirstchapter,weintroducedthepossibilityofdeclaringfunctionswith(namedfunction)orwithout(unnamedoranonymousfunction)explicitlyindicatingitsname,butwedidn'tmentionthatwewerealsousingtwodifferenttypesoffunction.

Inthefollowingexample,thenamedfunctiongreetNamedisafunctiondeclarationwhilegreetUnnamedisafunctionexpression.Ignorethefirsttwolines,whichcontaintwoconsolelogstatements,fornow:

console.log(greetNamed("John"));

console.log(greetUnnamed("John"));

functiongreetNamed(name:string):string{

if(name){

return"Hi!"+name;

}

}

vargreetUnnamed=function(name:string):string{

if(name){

return"Hi!"+name;

}

}

Wemightthinkthattheseprecedingfunctionsarereallysimilar,buttheywillbehavedifferently.Theinterpretercanevaluateafunctiondeclarationasitisbeingparsed.Ontheotherhand,thefunctionexpressionispartofanassignmentandwillnotbeevaluateduntiltheassignmenthasbeencompleted.

Note

Themaincauseofthedifferentbehaviorofthesefunctionsisaprocessknownasvariablehoisting.Wewilllearnmoreaboutthevariablehoistingprocesslaterinthischapter.

IfwecompiletheprecedingTypeScriptcodesnippetintoJavaScriptandtrytoexecuteitinawebbrowser,wewillobservethatthefirstalertstatementwillworkbecauseJavaScriptknowsaboutthedeclarationfunctionandcanparseitbeforetheprogramisexecuted.

However,thesecondalertstatementwillthrowanexception,whichindicatesthatgreetUnnamedisnotafunction.TheexceptionisthrownbecausethegreetUnnamedassignmentmustbecompletedbeforethefunctioncanbeevaluated.

Page 111: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctiontypesWealreadyknowthatitispossibletoexplicitlydeclarethetypeofanelementinourapplicationbyusingtheoptionaltypedeclarationannotation:

functiongreetNamed(name:string):string{

if(name){

return"Hi!"+name;

}

}

Intheprecedingfunction,wehavespecifiedthetypeoftheparametername(string)anditsreturntype(string).Sometimes,wewillneedtonotjustspecifythetypesofthefunctionelements,butalsothefunctionitself.Let'stakealookatanexample:

vargreetUnnamed:(name:string)=>string;

greetUnnamed=function(name:string):string{

if(name){

return"Hi!"+name;

}

}

Intheprecedingexample,wehavedeclaredthevariablegreetUnnamedanditstype.ThetypeofgreetUnnamedisafunctiontypethattakesastringvariablecallednameasitsonlyparameterandreturnsastringafterbeinginvoked.Afterdeclaringthevariable,afunction,whosetypemustbeequaltothevariabletype,isassignedtoit.

WecanalsodeclarethegreetUnnamedtypeandassignafunctiontoitinthesamelineratherthandeclaringitintwoseparatelineslikewedidinthepreviousexample:

vargreetUnnamed:(name:string)=>string=function(name:string):string

{

if(name){

return"Hi!"+name;

}

}

Justlikeinthepreviousexample,theprecedingcodesnippetalsodeclaresavariablegreetUnnamedanditstype.Wewillassignafunctiontothisvariableinthesamelineinwhichitisdeclared.Theassignedfunctionmustbeequaltothevariabletype.

Note

Intheprecedingexample,wehavedeclaredthetypeofthegreetUnnamedvariableandthenassignedafunctionasitsvalue.Thetypeofthefunctioncanbeinferredfromtheassignedfunction,andforthisreason,itisunnecessarytoaddaredundanttypeannotation.Wehavedonethistofacilitatetheunderstandingofthissection,butitisimportanttomentionthataddingredundanttypeannotationscanmakeourcodehardertoread,anditisconsideredbadpractice.

Page 112: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionswithoptionalparametersUnlikeJavaScript,theTypeScriptcompilerwillthrowanerrorifweattempttoinvokeafunctionwithoutprovidingtheexactnumberandtypeofparametersthatitssignaturedeclares.Let'stakealookatacodesampletodemonstrateit:

functionadd(foo:number,bar:number,foobar:number):number{

returnfoo+bar+foobar;

}

Theprecedingfunctioniscalledaddandwilltakethreenumbersasparameters:namedfoo,bar,andfoobar.Ifweattempttoinvokethisfunctionwithoutprovidingexactlythreenumbers,wewillgetacompilationerrorindicatingthatthesuppliedparametersdonotmatchthefunction'ssignature:

add();//Suppliedparametersdonotmatchanysignature

add(2,2);//Suppliedparametersdonotmatchanysignature

add(2,2,2);//returns6

Therearescenariosinwhichwemightwanttobeabletocallthefunctionwithoutprovidingallitsarguments.TypeScriptfeaturesoptionalparametersinfunctionstohelpustoincreasetheflexibilityofourfunctions.WecanindicatetoTypeScriptthatwewantafunction'sparametertobeoptionalbyappendingthecharacter?toitsname.Let'supdatethepreviousfunctiontotransformtherequiredparameterfoobarintoanoptionalparameter:

functionadd(foo:number,bar:number,foobar?:number):number{

varresult=foo+bar;

if(foobar!==undefined){

result+=foobar;

}

returnresult;

}

Notehowwehavechangedthefoobarparameternameintofoobar?,andhowwearecheckingthetypeoffoobarinsidethefunctiontoidentifyiftheparameterwassuppliedasanargumenttothefunctionornot.Afterdoingthesechanges,theTypeScriptcompilerwillallowustoinvokethefunctionwithouterrorswhenwesupplytwoorthreeargumentstoit:

add();//Suppliedparametersdonotmatchanysignature

add(2,2);//returns4

add(2,2,2);//returns6

Itisimportanttonotethattheoptionalparametersmustalwaysbelocatedaftertherequiredparametersinthefunction'sparameterslist.

Page 113: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionswithdefaultparametersWhenafunctionhassomeoptionalparameters,wemustcheckifanargumenthasbeenpassedtothefunction(justlikewedidinthepreviousexample).

Therearesomescenariosinwhichitwouldbemoreusefultoprovideadefaultvalueforaparameterwhenitisnotsuppliedthantomakeitanoptionalparameter.Let'srewritetheaddfunction(fromtheprevioussection)usingtheinlineifstructure:

functionadd(foo:number,bar:number,foobar?:number):number{

returnfoo+bar+(foobar!==undefined?foobar:0);

}

Thereisnothingwrongwiththeprecedingfunction,butwecanimproveitsreadabilitybyprovidingadefaultvalueforthefoobarparameterinsteadofflaggingitasanoptionalparameter:

functionadd(foo:number,bar:number,foobar:number=0):number{

returnfoo+bar+foobar;

}

Toindicatethatafunctionparameterisoptional,wejustneedtoprovideadefaultvalueusingthe=operatorwhendeclaringthefunction'ssignature.TheTypeScriptcompilerwillgenerateanifstructureintheJavaScriptoutputtosetadefaultvalueforthefoobarparameterifitisnotpassedasanargumenttothefunction:

functionadd(foo,bar,foobar){

if(foobar===void0){foobar=0;}

returnfoo+bar+foobar;

}

Void0isusedbytheTypeScriptcompilertocheckifavariableisequaltoundefined.Whilemostdevelopersusetheundefinedvariable,mostcompilersusevoid0.

Justlikeoptionalparameters,defaultparametersmustbealwayslocatedafteranyrequiredparametersinthefunction'sparameterlist.

Page 114: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionswithrestparametersWehaveseenhowtouseoptionalanddefaultparameterstoincreasethenumberofwaysthatwecaninvokeafunction.Let'sreturnonemoretimetothepreviousexample:

functionadd(foo:number,bar:number,foobar:number=0):number{

returnfoo+bar+foobar;

}

Wehaveseenhowtomakepossibletheusageoftheaddfunctionwithtwoorthreeparameters,butwhatifwewantedtoallowotherdeveloperstopassfourorfiveparameterstoourfunction?Wewouldhavetoaddtwoextradefaultoroptionalparameters.Andwhatifwewantedtoallowthemtopassasmanyparametersastheymayneed?Thesolutiontothispossiblescenarioistheuseofrestparameters.Therestparametersyntaxallowsustorepresentanindefinitenumberofargumentsasanarray:

functionadd(...foo:number[]):number{

varresult=0;

for(vari=0;i<foo.length;i++){

result+=foo[i];

}

returnresult;

}

Aswecanseeinthefollowingcodesnippet,wehavereplacedthefunctionparametersfoo,bar,andfoobarwithjustoneparameter:foo.Notethatthenameoftheparameterfooisprecededbyanellipsis(asetofthreeperiods—nottheactualellipsischaracter).Arestparametermustbeofanarraytypeorwewillgetacompilationerror.Wecannowinvoketheaddfunctionwithasmanyparametersaswemayneed:

add();//returns0

add(2);//returns2

add(2,2);//returns4

add(2,2,2);//returns6

add(2,2,2,2);//returns8

add(2,2,2,2,2);//returns10

add(2,2,2,2,2,2);//returns12

Althoughthereisnospecificlimittothetheoreticalmaximumnumberofargumentsthatafunctioncantake,thereare,ofcourse,practicallimits.Theselimitsareentirelyimplementation-dependentand,mostlikely,willalsodependexactlyonhowwearecallingthefunction.

JavaScriptfunctionshaveabuilt-inobjectcalledtheargumentsobject.Thisobjectisavailableasalocalvariablenamedarguments.Theargumentsvariablecontainsanobjectsimilartoanarray,whichcontainstheargumentsusedwhenthefunctionwasinvoked.

Note

Page 115: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theargumentsobjectexposessomeofthemethodsandpropertiesprovidedbyastandardarray,butnotallofthem.Refertothecompletereferenceathttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/argumentstolearnmoreaboutitspeculiarities.

IfweexaminetheJavaScriptoutput,wewillnoticethatTypeScriptiteratestheargumentsobjectinordertoaddthevaluestothefoovariable:

functionadd(){

varfoo=[];

for(var_i=0;_i<arguments.length;_i++){

foo[_i-0]=arguments[_i];

}

varresult=0;

for(vari=0;i<foo.length;i++){

result+=foo[i];

}

returnresult;

}

Wecanarguethatthisisanextra,unnecessaryiterationoverthefunction'sparameters.Eventhoughishardtoimaginethisextraiterationbecomingaperformanceissue,ifyouthinkthatthiscouldbeaproblemfortheperformanceofyourapplication,youmaywanttoconsideravoidingusingrestparametersanduseanarrayastheonlyparameterofthefunctioninstead:

functionadd(foo:number[]):number{

varresult=0;

for(vari=0;i<foo.length;i++){

result+=foo[i];

}

returnresult;

}

Theprecedingfunctiontakesanarrayofnumbersasitsonlyparameter.TheinvocationAPIwillbealittledifferentfromtherestparameters,butwewilleffectivelyavoidtheextraiterationoverthefunction'sargumentlist:

add();//Suppliedparametersdonotmatchanysignature

add(2);//Suppliedparametersdonotmatchanysignature

add(2,2);//Suppliedparametersdonotmatchanysignature

add(2,2,2);//Suppliedparametersdonotmatchanysignature

add([]);//returns0

add([2]);//returns2

add([2,2]);//returns4

add([2,2,2]);//returns6

Page 116: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionoverloadingFunctionoverloadingormethodoverloadingistheabilitytocreatemultiplemethodswiththesamenameandadifferentnumberofparametersortypes.InTypeScript,wecanoverloadafunctionbyspecifyingallfunctionsignaturesofafunction,followedbyasignatureknownastheimplementationsignature.Let'stakealookatanexample:

functiontest(name:string):string;//overloadedsignature

functiontest(age:number):string;//overloadedsignature

functiontest(single:boolean):string;//overloadedsignature

functiontest(value:(string|number|boolean)):string;{//implementation

signature

switch(typeofvalue){

case"string":

return`Mynameis${value}.`;

case"number":

return`I'm${value}yearsold.`;

case"boolean":

returnvalue?"I'msingle.":"I'mnotsingle.";

default:

console.log("InvalidOperation!");

}

}

Note

Youmightnotbefamiliarwiththesyntaxusedinsomeofthestringsintheprecedingcodesnippet.ThissyntaxisknownasTemplateStrings.Templatestringsareenclosedbytheback-tick(``)characterinsteadofdoubleorsinglequotes.Templatestringscancontainplaceholders.Theseareindicatedbythedollarsignandcurlybraces(${expression}).Theexpressionsintheplaceholdersandthetextbetweenthemgetpassedtoafunction.Thedefaultfunctionjustconcatenatesthepartsintoasinglestring.

Aswecanseeintheprecedingexample,wehaveoverloadedthefunctiontestthreetimesbyaddingasignaturethattakesastringasitsonlyparameter,anotherfunctionthattakesanumber,andafinalsignaturethattakesaBooleanasitsuniqueparameter.Itisimportanttonotethatallfunctionsignaturesmustbecompatible;soif,forexample,oneofthesignaturestriestoreturnanumberwhileanothertriestoreturnastring,wewillgetacompilationerror.

Theimplementationsignaturemustbecompatiblewithalltheoverloadedsignatures,alwaysbethelastinthelist,andtakeanyorauniontypeasthetypeofitsparameters.

Invokingtheimplementationsignaturedirectlywillcauseacompilationerror:

test("Remo");//returns"MynameisRemo."

test(26);//returns"I'm26yearsold.";

test(false);//returns"I'mnotsingle.";

test({custom:"custom"});//error

Page 117: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SpecializedoverloadingsignaturesWecanuseaspecializedsignaturetocreatemultiplemethodswiththesamenameandnumberofparametersbutadifferentreturntype.Tocreateaspecializedsignature,wemustindicatethetypeoffunctionparameterusingastring.Thestringliteralisusedtoidentifywhichofthefunctionoverloadsisinvoked:

interfaceDocument{

createElement(tagName:"div"):HTMLDivElement;//specialized

createElement(tagName:"span"):HTMLSpanElement;//specialized

createElement(tagName:"canvas"):HTMLCanvasElement;//specialized

createElement(tagName:string):HTMLElement;//non-specialized

}

Intheprecedingexample,wehavedeclaredthreespecializedoverloadedsignaturesandonenon-specializedsignatureforthefunctionnamedcreateElement.

Whenwedeclareaspecializedsignatureinanobject,itmustbeassignabletoatleastonenon-specializedsignatureinthesameobject.Thiscanbeobservedintheprecedingexample,asthecreateElementpropertybelongstoatypethatcontainsthreespecializedsignatures,allofwhichareassignabletothenon-specializedsignatureinthetype.

Whenwritingoverloadeddeclarations,wemustlistthenon-specializedsignaturelast.

Note

Rememberthat,asseeninChapter1,IntroducingTypeScript,wecanalsouseuniontypestocreateamethodwiththesamenameandnumberofparametersbutadifferenttype.

Page 118: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionscopeLow-levellanguagessuchasChavelow-levelmemorymanagementfeatures.InprogramminglanguageswithahigherlevelofabstractionsuchasTypeScript,valuesareallocatedwhenvariablesarecreatedandautomaticallyclearedfrommemorywhentheyarenotusedanymore.TheprocessthatcleansthememoryisknownasgarbagecollectionandisperformedbytheJavaScriptruntimegarbagecollector.

Thegarbagecollectorgenerallydoesagreatjob,butitisamistaketoassumethatitwillalwayspreventusfromfacingamemoryleak.Thegarbagecollectorwillclearavariablefromthememorywheneverthevariableisoutofthescope.IsimportanttounderstandhowtheTypeScriptscopeworkssoweunderstandthelifecycleofthevariables.

Someprogramminglanguagesusethestructureoftheprogramsourcecodetodeterminewhatvariableswearereferringto(lexicalscoping),whileothersusetheruntimestateoftheprogramstacktodeterminewhatvariablewearereferringto(dynamicscoping).Themajorityofmodernprograminglanguagesuselexicalscoping(includingTypeScript).Lexicalscopingtendstobedramaticallyeasiertounderstandforbothhumansandanalysistoolsthandynamicscoping.

Whileinmostlexicalscopedprogramminglanguages,variablesarescopedtoablock(asectionofcodedelimitedbycurlybraces{}),inTypeScript(andJavaScript),variablesarescopedtoafunction:

functionfoo():void{

if(true){

varbar:number=0;

}

alert(bar);

}

foo();//shows0

Theprecedingfunctionnamedfoocontainsanifstructure.Wehavedeclaredanumericvariablenamedbarinsidetheifstructure,andlaterwehaveattemptedtoshowthevalueofthevariablebarusingthealertfunction.

Wemightthinkthattheprecedingcodesamplewouldthrowanerrorinthefifthlinebecausethebarvariableshouldbeoutofthescopewhenthealertfunctionisinvoked.However,ifweinvokethefoofunction,thealertfunctionwillbeabletodisplaythevariablebarwithouterrorsbecauseallthevariablesinsideafunctionwillbeinthescopeoftheentirefunctionbody,eveniftheyareinsideanotherblockofcode(exceptafunctionblock).

Thismightseemreallyconfusing,butitiseasytounderstandonceweknowthat,atruntime,allthevariabledeclarationsaremovedtothetopofafunctionbeforethefunctionisexecuted.Thisbehavioriscalledhoisting.

Page 119: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

TypeScriptiscompiledtoJavaScriptandthenexecuted—thismeansthataTypeScriptapplicationisaJavaScriptapplicationatruntime,andforthisreason,whenwerefertotheTypeScriptruntime,wearetalkingabouttheJavaScriptruntime.WewilllearnindepthabouttheruntimeinChapter5,Runtime.

So,beforetheprecedingcodesnippetisexecuted,theruntimewillmovethedeclarationofthevariablebartothetopofourfunction:

functionfoo():void{

varbar:number;

if(true){

bar=0;

}

alert(bar);

}

Thismeansthatwecanuseavariablebeforeitisdeclared.Let'stakealookatanexample:

functionfoo2():void{

bar=0;

varbar:number;

alert(bar);

}

foo2();

Intheprecedingcodesnippet,wehavedeclaredafunctionfoo2,andinitsbody,wehaveassignedthevalue0toavariablenamedbar.Atthispoint,thevariablehasnotbeendeclared.Inthesecondline,weareactuallydeclaringthevariablebaranditstype.Inthelastline,wearedisplayingthevalueofbarusingthealertfunction.

Becausedeclaringavariableanywhereinsideafunction(exceptanotherfunction)isequivalenttodeclaringitatthetopofthefunction,thefoo2functionistransformedintothefollowingatruntime:

functionfoo2():void{

varbar:number;

bar=0;

alert(bar);

}

foo2();

BecausedeveloperswithaJavaorC#backgroundarenotusedtothefunctionscope,itisoneofthemostcriticizedcharacteristicsofJavaScript.ThepeopleinchargeofthedevelopmentoftheECMAScript6specificationareawareofthisand,asaresult,theyhaveintroducedthekeywordsletandconst.

Page 120: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theletkeywordallowsustosetthescopeofavariabletoablock(if,while,for…)ratherthanafunctionblock.Wecanupdatethefirstexampleinthissectiontoshowcasehowletworks:

functionfoo():void{

if(true){

letbar:number=0;

bar=1;

}

alert(bar);//error

}

Thebarvariableisnowdeclaredusingtheletkeywordand,asaresult,itisonlyaccessibleinsidetheifblock.Thevariableisnothoistedtothetopofthefoofunctionandcannotbeaccessedbythealertfunctionoutsidetheifstatement.

Whilevariablesdefinedwithconstfollowthesamescoperulesasvariablesdeclaredwithlet,theycan'tbereassigned:

functionfoo():void{

if(true){

constbar:number=0;

bar=1;//error

}

alert(bar);//error

}

Ifweattempttocompiletheprecedingcodesnippet,wewillgetanerrorbecausethebarvariableisnotaccessibleoutsidetheifstatement(justlikewhenweusedtheletkeyword),andanewerroroccurswhenwetrytoassignanewvaluetothebarvariable.Theseconderroriscausedbecauseitisnotpossibletoassignavaluetoaconstantvariableoncethevariablehasalreadybeeninitialized.

Page 121: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImmediatelyinvokedfunctionsAnimmediatelyinvokedfunctionexpression(IIFE)isadesignpatternthatproducesalexicalscopeusingfunctionscoping.IIFEcanbeusedtoavoidvariablehoistingfromwithinblocksortopreventusfrompollutingtheglobalscope.Forexample:

varbar=0;//global

(function(){

varfoo:number=0;//inscopeofthisfunction

bar=1;//inglobalscope

console.log(bar);//1

console.log(foo);//0

})();

console.log(bar);//1

console.log(foo);//error

Intheprecedingexample,wehavewrappedthedeclarationoftwovariables(fooandbar)withanIIFE.ThefoovariableisscopedtotheIIFEfunctionandisnotavailableintheglobalscope,whichexplainstheerrorwhentryingtoaccessitinthelastline.

WecanalsopassavariabletotheIIFEtohavebettercontroloverthecreationofvariablesoutsideitsownscope:

varbar=0;//global

(function(global){

varfoo:number=0;//inscopeofthisfunction

bar=1;//inglobalscope

console.log(global.bar);//1

console.log(foo);//0

})(this);

console.log(bar);//1

console.log(foo);//error

Thistime,theIIFEtakesthethisoperatorasitsonlyargument,whichpointstotheglobalscope,becausewearenotinvokingthethisoperatorfromwithinafunction.InsidetheIIFE,thethisoperatorispassedasaparameternamedglobal.Wecanthenachievemuchbettercontrolovertheobjectswewanttodeclareintheglobalscope(bar)andthosewedon't(foo).

Furthermore,IIFEcanhelpustosimultaneouslyallowpublicaccesstomethodswhileretainingprivacyforvariablesdefinedwithinthefunction.Let'stakealookatanexample:

classCounter{

private_i:number;

constructor(){

this._i=0;

}

get():number{

Page 122: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnthis._i;

}

set(val:number):void{

this._i=val;

}

increment():void{

this._i++;

}

}

varcounter=newCounter();

console.log(counter.get());//0

counter.set(2);

console.log(counter.get());//2

counter.increment();

console.log(counter.get());//3

console.log(counter._i);//Error:Property'_i'isprivate

Note

Byconvention,TypeScriptandJavaScriptdevelopersusuallynameprivatevariableswithnamesprecededbyanunderscore(_).

WehavedefinedaclassnamedCounterthathasaprivatenumericattributenamed_i.Theclassalsohasmethodstogetandsetthevalueoftheprivateproperty_i.WehavealsocreatedaninstanceoftheCounterclassandinvokedthemethodsset,get,andincrementtoobservethateverythingisworkingasexpected.Ifweattempttoaccessthe_ipropertyinaninstanceofCounter,wewillgetanerrorbecausethevariableisprivate.

IfwecompiletheprecedingTypeScriptcode(onlytheclassdefinition)andexaminethegeneratedJavaScriptcode,wewillseethefollowing:

varCounter=(function(){

functionCounter(){

this._i=0;

}

Counter.prototype.get=function(){

returnthis._i;

};

Counter.prototype.set=function(val){

this._i=val;

};

Counter.prototype.increment=function(){

this._i++;

};

returnCounter;

})();

ThisgeneratedJavaScriptcodewillworkperfectlyinmostscenarios,butifweexecuteitinabrowserandtrytocreateaninstanceofCounterandaccessitsproperty_i,wewillnotgetanyerrorsbecauseTypeScriptwillnotgenerateruntimeprivatepropertiesforus.Sometimeswewillneedtowriteourfunctionsinsuchawaythatsomepropertiesareprivateatruntime,forexample,ifwereleasealibrarythatwillbeusedbyJavaScriptdevelopers.Wecanuse

Page 123: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IIFEtosimultaneouslyallowpublicaccesstomethodswhileretainingprivacyforvariablesdefinedwithinthefunction:

varCounter=(function(){

var_i:number=0;

functionCounter(){

}

Counter.prototype.get=function(){

return_i;

};

Counter.prototype.set=function(val:number){

_i=val;

};

Counter.prototype.increment=function(){

_i++;

};

returnCounter;

})();

Intheprecedingexample,everythingisalmostidenticaltoTypeScript'sgeneratedJavaScript,exceptthatthevariable_ibeforewasanattributeoftheCounterclass,andnowitisanobjectintheCounterclosure.

Note

Closuresarefunctionsthatrefertoindependent(free)variables.Inotherwords,thefunctiondefinedintheclosurerememberstheenvironment(variablesinthescope)inwhichitwascreated.WewilldiscovermoreaboutclosuresinChapter5,Runtime.

Ifwerunthegeneratedoutputinabrowserandtrytoinvokethe_ipropertydirectly,wewillnoticethatthepropertyisnowprivateatruntime:

varcounter=newCounter();

console.log(counter.get());//0

counter.set(2);

console.log(counter.get());//2

counter.increment();

console.log(counter.get());//3

console.log(counter._i);//undefined

Note

Insomecases,wewillneedtohavereallyprecisecontroloverscopeandclosures,andourcodewillenduplookingmuchmorelikeJavaScript.Justrememberthat,aslongaswewriteourapplicationcomponents(classes,modules,andsoon)tobeconsumedbyotherTypeScriptcomponents,wewillrarelyhavetoworryaboutimplementingruntimeprivateproperties.WewilllookindepthattheTypeScriptruntimeinChapter5,Runtime.

Page 124: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GenericsAndyHuntandDaveThomasformulatedthedon'trepeatyourself(DRY)principleinthebookThePragmaticProgrammer.TheDRYprincipleaimstoreducetherepetitionofinformationofallkinds.WewillnowtakealookatanexamplethatwillhelpustounderstandwhatgenericsfunctionsareandhowtheycanhelpusfollowtheDRYprinciple.

WewillstartbydeclaringareallysimpleUserclass:

classUser{

name:string;

age:number;

}

NowthatwehaveourUserclassinplace,let'swriteafunctionnamedgetUsersthatwillrequestalistofusersviaAJAX:

functiongetUsers(cb:(users:User[])=>void):void{

$.ajax({

url:"/api/users",

method:"GET",

success:function(data){

cb(data.items);

},

error:function(error){

cb(null);

}

});

}

Note

WewillusejQueryinthisexample.Remembertocreateapackage.jsonfileandinstallthejQuerypackageusingnpm.YouwillalsoneedtoinstallthejQuerytypedefinitionsfileusingtsd.RefertoChapter1,IntroducingTypescriptandChapter2,AutomatingYourDevelopmentWorkflowifyouneedadditionalhelp.

ThegetUsersfunctiontakesafunctionasaparameterthatwillbeinvokediftheAJAXrequesthasbeensuccessful.Itcanbeinvokedasfollows:

getUsers(function(users:User[]){

for(vari;users.length;i++){

console.log(users[i].name);

}

});

Nowlet'simaginethatweneedanalmostidenticaloperation.Butthistime,wewilluseanOrderentityinstead:

classOrder{

id:number;

Page 125: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

total:number;

items:any[]

}

ThegetOrdersfunctionisalmostidenticaltothegetUsersfunction.ItusesadifferentURLanditwillpassanarrayofOrdersinsteadofaUserarray:

functiongetOrders(cb:(orders:Order[])=>void):void{

$.ajax({

url:"/api/orders",

method:"GET",

success:function(data){

cb(data.items);

},

error:function(error){

cb(null);

}

});

}

getOrders(function(orders:Orders[]){

for(vari;orders.length;i++){

console.log(orders[i].total);

}

});

Wecanusegenericstoavoidthiskindofrepetition.Genericprogrammingisastyleofcomputerprogramminginwhichalgorithmsarewrittenintermsoftypestobespecifiedlater.Thesetypesaretheninstantiatedwhenneededforspecifictypesprovidedasparameters.WearegoingtowriteagenericfunctionnamedgetEntitiesthattakestwoparameters:

functiongetEntities<T>(url:string,cb:(list:T[])=>void):void{

$.ajax({

url:url,

method:"GET",

success:function(data){

cb(data.items);

},

error:function(error){

cb(null);

}

});

}

Wehaveaddedanglebrackets(<>)afterthenameofourfunctionstoindicatethatitisagenericfunction.EnclosedintheanglebracketsisthecharacterT,whichisusedtorefertoatype.Thefirstparameterisnamedurlandisastring;thesecondparameterisafunctionnamedcb,whichtakesaparameterlistoftypeTasitsonlyparameter.

WecannowusethisgenericfunctiontoindicatewhattypeTwillrepresent:

getEntities<User>("/api/users",function(users:Users[]){

for(vari;users.length;i++){

Page 126: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

console.log(users[i].name);

}

});

getEntities<Order>("/api/orders",function(orders:Orders[]){

for(vari;orders.length;i++){

console.log(orders[i].total);

}

});

Page 127: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TagfunctionsandtaggedtemplatesWehavealreadyseenhowtoworkwithtemplatestringssuchasthefollowing:

varname='remo';

varsurname=jansen;

varhtml=`<h1>${name}${surname}</h1>`;

However,thereisoneuseoftemplatestringsthatwedeliberatelyskippedbecauseitiscloselyrelatedtotheuseofaspecialkindoffunctionknownastagfunction.

Wecanuseatagfunctiontoextendormodifythestandardbehavioroftemplatestrings.Whenweapplyatagfunctiontoatemplatestring,thetemplatestringbecomesataggedtemplate.

WearegoingtoimplementatagfunctionnamedhtmlEscape.Touseatagfunction,wemustusethenameofthefunctionfollowedbyatemplatestring:

varhtml=htmlEscape`<h1>${name}${surname}</h1>`;

Atagtemplatemustreturnastringandtakethefollowingarguments:

Anarraywhichcontainsallthestaticliteralsinthetemplatestring(<h1>and</h1>intheprecedingexample)ispassedasthefirstargument.Arestparameterispassedasthesecondparameter.Therestparametercontainsallthevaluesinthetemplatestring(nameandsurnameintheprecedingexample).

Wenowknowthesignatureofatagfunction.

tag(literals:string[],...values:any[]):string

Let'simplementthehtmlEscapetagfunction:

functionhtmlEscape(literals,...placeholders){

letresult="";

for(leti=0;i<placeholders.length;i++){

result+=literals[i];

result+=placeholders[i]

.replace(/&/g,'&amp;')

.replace(/"/g,'&quot;')

.replace(/'/g,''')

.replace(/</g,'&lt;')

.replace(/>/g,'&gt;');

}

result+=literals[literals.length-1];

returnresult;

}

TheprecedingfunctioniteratesthroughtheliteralsandvaluesandensuresthattheHTMLcodeisescapedfromthevaluestoavoidpossiblecodeinjectionattacks.

Themainbenefitofusingataggedfunctionisthatitallowsustocreatecustomtemplate

Page 128: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

stringprocessors.

Note

ThisfeaturewillbeavailableintheTypeScript1.6release.

Page 129: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AsynchronousprogramminginTypeScriptNowthatwehaveseenhowtoworkwithfunctions,wewillexplorehowwecanusethem,togetherwithsomenativeobjects,towriteasynchronousapplications.

Page 130: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Callbacksandhigher-orderfunctionsInTypeScript,functionscanbepassedasargumentstoanotherfunction.Thefunctionpassedtoanotherasanargumentisknownasacallback.Functionscanalsobereturnedbyanotherfunction.Thefunctionsthatacceptfunctionsasparameters(callbacks)orreturnfunctionsasanargumentareknownashigher-orderfunctions.Callbacksareusuallyanonymousfunctions.

varfoo=function(){//callback

console.log('foo');

}

functionbar(cb:()=>void){//higherorderfunction

console.log('bar');

cb();

}

bar(foo);//prints'bar'thenprints'foo'

Page 131: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ArrowfunctionsInTypeScript,wecandeclareafunctionusingafunctionexpressionoranarrowfunction.Anarrowfunctionexpressionhasashortersyntaxcomparedtofunctionexpressionsandlexicallybindsthevalueofthethisoperator.

ThethisoperatorbehavesalittledifferentlyinTypeScriptcomparedtootherlanguages.WhenwedefineaclassinTypeScript,wecanusethethisoperatortorefertotheclass'sownproperties.Let'stakealookatanexample:

classPerson{

name:string;

constructor(name:string){

this.name=name;

}

greet(){

alert(`Hi!Mynameis${this.name}`);

}

}

varremo=newPerson("Remo");

remo.greet();//"Hi!MynameisRemo"

WehavedefinedaPersonclassthatcontainsapropertyoftypestringcalledname.Theclasshasaconstructorandamethodgreet.Wehavecreatedaninstancenamedremoandinvokedthemethodnamedgreet,whichinternallyusesthethisoperatortoaccesstheremoproperty'sname.Insidethegreetmethod,thethisoperatorpointstotheobjectthatenclosesthegreetmethod.

Wemustbecarefulwhenusingthethisoperatorbecauseinsomescenariositcanpointtothewrongvalue.Let'saddanextramethodtothepreviousexample:

classPerson{

name:string;

constructor(name:string){

this.name=name;

}

greet(){

alert(`Hi!Mynameis${this.name}`);

}

greetDelay(time:number){

setTimeout(function(){

alert(`Hi!Mynameis${this.name}`);

},time);

}

}

varremo=newPerson("remo");

remo.greet();//"Hi!Mynameisremo"

remo.greetDelay(1000);//"Hi!Mynameis"

InthegreetDelaymethod,weperformanalmostidenticaloperationtotheoneperformedbythegreetmethod.Thistimethefunctiontakesaparameternamedtime,whichisusedtodelay

Page 132: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thegreetmessage.

Inordertodelaythemessage,weusethesetTimeoutfunctionandacallback.Assoonaswedefineananonymousfunction(thecallback),thethiskeywordchangesitsvalueandstartspointingtotheanonymousfunction.ThisexplainswhythenameremoisnotdisplayedbythegreetDelaymessage.

Asmentioned,anarrowfunctionexpressionlexicallybindsthevalueofthethisoperator.Thismeansthatitallowsustoaddafunctionwithoutalteringthevalueofthisoperator.Let'sreplacethefunctionexpressionfromthepreviousexamplewithanarrowfunction:

classPerson{

name:string;

constructor(name:string){

this.name=name;

}

greet(){

alert(`Hi!Mynameis${this.name}`);

}

greetDelay(time:number){

setTimeout(()=>{

alert(`Hi!Mynameis${this.name}`);

},time);

}

}

varremo=newPerson("remo");

remo.greet();//"Hi!Mynameisremo"

remo.greetDelay(1000);//"Hi!Mynameisremo"

Byusinganarrowfunction,wecanensurethatthethisoperatorstillpointstothePersoninstanceandnottothesetTimeoutcallback.IfweexecutethegreetDelayfunction,thenamepropertywillbedisplayedasexpected.

ThefollowingpieceofcodewasgeneratedbytheTypeScriptcompiler.Whencompilinganarrowfunction,theTypeScriptcompilerwillgenerateanaliasforthethisoperatornamed_this.Thealiasisusedtoensurethatthethisoperatorpointstotherightobject.

Person.prototype.greetDelay=function(time){

var_this=this;

setTimeout(function(){

alert("Hi!Mynameis"+_this.name);

},time);

};

Page 133: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CallbackhellWehaveseenthatcallbacksandhigherorderfunctionsaretwopowerfulandflexibleTypeScriptfeatures.However,theuseofcallbackscanleadtoamaintainabilityissueknownascallbackhell.Wewillnowwriteareal-lifeexampletoshowcasewhatacallbackhellisandhoweasilywecanendupdealingwithit.

Note

Rememberthatyoucanfindthecompletesourcecodeforthisdemointhecompanionsourcecode.

WearegoingtoneedhandlebarsandjQuerylibraries,solet'sinstallthesetwolibrariesandtheirrespectivetypedefinitionfilesusingnpmandtsd.Wecanthenimporttheirtypedefinitions:

///<referencepath="../typings/handlebars/handlebars.d.ts"/>

///<referencepath="../typings/jquery/jquery.d.ts"/>

Tomakeourcodeeasiertoread,wewillcreateanaliasforthecallbacktype:

typecb=(json:any)=>void;

NowweneedtodeclareourViewclass.TheViewclasshassomepropertiesthatallowustosetthefollowingproperties:

Container:TheDOMselectorwherewewantourviewtobeinsertedTemplateURL:TheURLthatwillreturnahandlebarstemplateServiceURL:TheURLofawebservicethatwillreturnsomeJSONdataArguments:Thedatatobesendtotheservice

WecanseetheViewclassimplementationasfollows:

classView{

private_container:string;

private_templateUrl:string;

private_serviceUrl:string;

private_args:any;

constructor(config){

this._container=config.container;

this._templateUrl=config.templateUrl;

this._serviceUrl=config.serviceUrl;

this._args=config.args;

}

//...

Afterdefiningtheclassconstructoranditsproperties,wewilladdaprivatemethodnamed_loadJsontoourclass.ThismethodtakestheserviceURL,thearguments,asuccesscallback,andanerrorcallbackasitsarguments.Insidethemethod,wewillsendajQueryAJAXrequestusingtheserviceURLandargumentsettings:

Page 134: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

private_loadJson(url:string,args:any,cb:cb,errorCb:cb){

$.ajax({

url:url,

type:"GET",

dataType:"json",

data:args,

success:(json)=>{

cb(json);

},

error:(e)=>{

errorCb(e);

}

});

}

//...

Note

HandlebarsisalibrarythatallowsustocompileandrenderHTMLtemplatesinabrowser.ThesetemplateshelpwithJSON-to-HTMLtransformations.Wewillmentionthislibrarylateracoupleoftimes,butdon'tworryifyouhaveneveruseditbefore;thissectionisnotabouthandlebars.

Thissectionisaboutasetoftasksandhowwecancontroltheexecutionflowofthosetasksusingcallbacks.Ifyouwanttolearnmoreabouthandlebars,visithttp://handlebarsjs.com/.

Thisfunctionisalmostidenticaltothepreviousone,butinsteadofloadingsomeJSON,wewillloadahandlebarstemplate:

private_loadHbs(url:string,cb:cb,errorCb:cb){

$.ajax({

url:url,

type:"GET",

dataType:"text",

success:(hbs)=>{

cb(hbs);

},

error:(e)=>{

errorCb(e);

}

});

}

//...

Thisfunctiontakesahandlebartemplatecodeasinputandtriestocompileitusingthehandlebarscompilefunction.Justlikeinthepreviousexample,weusecallbacks,whichwillbeinvokedafterthesuccessorfailureoftheoperation:

private_compileHbs(hbs:string,cb:cb,errorCb:cb){

try

{

vartemplate=Handlebars.compile(hbs);

cb(template);

Page 135: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

catch(e){

errorCb(e);

}

}

//...

Inthisfunction,wetakethealreadycompiledtemplateandthealreadyloadedJSONdataandputthemtogethertotransformJSONintoHTMLfollowingthetemplateformattingrules.Justlikeinthepreviousexample,weusecallbacksthatwillbeinvokedafterthesuccessorfailureoftheoperation:

private_jsonToHtml(template:any,json:any,cb:cb,errorCb:cb){

try

{

varhtml=template(json);

cb(html);

}

catch(e){

errorCb(e);

}

}

//...

ThefollowingfunctiontakestheHTMLgeneratedbythe_jsonToHtmlfunctionandappendsittoaDOMelement:

private_appendHtml=function(html:string,cb:cb,errorCb:cb){

try

{

if($(this._container).length===0){

thrownewError("Containernotfound!");

}

$(this._container).html(html);

cb($(this._container));

}

catch(e){

errorCb(e);

}

}

//...

Nowthatwehaveafewfunctionsthatusecallbacks,wewilluseallofthemtogetherinonesinglefunctionnamedrender.Therendermethodcontrolstheexecutionflowofthetasks,andexecutestheminthefollowingorder:

1. LoadstheJSONdata.2. Loadsthetemplate.3. Compilesthetemplate.4. TransformsJSONintoHTML.5. AppendsHTMLtotheDOM.

Page 136: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Eachtasktakesasuccesscallback,whichinvokesthefollowingtasksinthelistifitissuccessful,andanerrorcallback,whichisinvokedwhensomethinggoeswrong:

publicrender(cb:cb,errorCb:cb){

try

{

this._loadJson(this._serviceUrl,this._args,(json)=>{

this._loadHbs(this._templateUrl,(hbs)=>{

this._compileHbs(hbs,(template)=>{

this._jsonToHtml(template,json,(html)=>{

this._appendHtml(html,cb);

},errorCb);

},errorCb);

},errorCb);

},errorCb);

}

catch(e){

errorCb(e);

}

}

}

Ingeneral,youshouldtrytoavoidnestingcallbackslikeintheprecedingexamplebecauseitwill:

MakethecodehardertounderstandMakethecodehardertomaintain(refactor,reuse,andsoon)Makeexceptionhandlingmoredifficult

Page 137: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PromisesAfterseeinghowtheuseofcallbackscanleadtosomemaintainabilityproblems,wewillnowlookatpromisesandhowtheycanbeusedtowritebetterasynchronouscode.Thecoreideabehindpromisesisthatapromiserepresentstheresultofanasynchronousoperation.Promisemustbeinoneofthethreefollowingstates:

Pending:TheinitialstateofapromiseFulfilled:ThestateofapromiserepresentingasuccessfuloperationRejected:Thestateofapromiserepresentingafailedoperation

Onceapromiseisfulfilledorrejected,itsstatecanneverchangeagain.Let'stakealookatthebasicsyntaxofapromise:

functionfoo(){

returnnewPromise((fulfill,reject)=>{

try

{

//dosomething

fulfill(value);

}

catch(e){

reject(reason);

}

});

}

foo().then(function(value){console.log(value);})

.catch(function(e){console.log(e);});

Note

Atry…catchstatementisusedheretoshowcasehowwecanexplicitlyfulfillorrejectapromise.Thetry…catchstatementisnotreallyneededinaPromisefunctionbecausewhenanerroristhrowninapromise,thepromisewillautomaticallyberejected.

Theprecedingcodesnippetdeclaresafunctionnamedfoothatreturnsapromise.Thepromisecontainsamethodnamedthen,whichacceptsafunctiontobeinvokedwhenthepromiseisfulfilled.Promisesalsoprovideamethodnamedcatch,whichisinvokedwhenapromiseisrejected.

Wewillnowreturntothecallbackhellexampleandmakesomechangesinthecodetousepromisesinsteadofcallbacks.

Justlikebefore,wearegoingtoneedhandlebarsandjQuery;solet'simporttheirtypedefinitions.Inaddition,thistime,wewillalsoneedthedeclarationsofalibraryknownasQ:

///<referencepath="../typings/handlebars/handlebars.d.ts"/>

///<referencepath="../typings/jquery/jquery.d.ts"/>

///<referencepath="../typings/q/q.d.ts"/>

Page 138: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

WewillusethePromiseobjectfromalibraryinsteadofthenativeobjectbecausethelibrariesimplementfallbackssoourcodecanworkinoldbrowsers.WewilluseapromiseslibraryknownasQ(version1.0.1)inthisexample.Ifyouwanttolearnmoreaboutit,visithttps://github.com/kriskowal/q.

TheclassnamehaschangedfromViewtoViewAsyncbuteverythingelseisstillidenticaltothepreviousexample:

classViewAsync{

private_container:string;

private_templateUrl:string;

private_serviceUrl:string;

private_args:any;

constructor(config){

this._container=config.container;

this._templateUrl=config.templateUrl;

this._serviceUrl=config.serviceUrl;

this._args=config.args;

}

//...

Note

ManydevelopersappendthewordAsynctothenameofafunctionasacodeconvention,whichisusedtoindicatethatafunctionisanasynchronousfunction.

Wewilluseourfirstpromiseinthefunction_loadJsonAsync.Thisfunctionwasnamed_loadJsoninthecallbackexample.Wehaveremovedthecallbacksforsuccessanderrorpreviouslydeclaredinthefunctionsignature.Finally,wehavewrappedthefunctionwithapromiseobjectandinvokedtheresolveandrejectmethodswhenthepromisesucceedsorfailsrespectively.

private_loadJsonAsync(url:string,args:any){

returnQ.Promise(function(resolve,reject){

$.ajax({

url:url,

type:"GET",

dataType:"json",

data:args,

success:(json)=>{

resolve(json);

},

error:(e)=>{

reject(e);

}

});

});

}

//...

Page 139: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wewillthenrefactor(rename,removecallbacks,wraplogicwithapromise,andsoon)eachoftheclassfunctions(_loadHbsAsync,compileHbsAsync,and_appendHtmlAsync):

private_loadHbsAsync(url:string){

returnQ.Promise(function(resolve,reject){

$.ajax({

url:url,

type:"GET",

dataType:"text",

success:(hbs)=>{

resolve(hbs);

},

error:(e)=>{

reject(e);

}

});

});

}

private_compileHbsAsync(hbs:string){

returnQ.Promise(function(resolve,reject){

try

{

vartemplate:any=Handlebars.compile(hbs);

resolve(template);

}

catch(e){

reject(e);

}

});

}

private_jsonToHtmlAsync(template:any,json:any){

returnQ.Promise(function(resolve,reject){

try

{

varhtml=template(json);

resolve(html);

}

catch(e){

reject(e);

}

});

}

private_appendHtmlAsync(html:string,container:string){

returnQ.Promise((resolve,reject)=>{

try

{

var$container:any=$(container);

if($container.length===0){

thrownewError("Containernotfound!");

}

$container.html(html);

resolve($container);

}

catch(e){

reject(e);

}

Page 140: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

});

}

//...

TheRenderAsyncmethod(previouslynamedrender)willpresentsomesignificantdifferences.

Inthefollowingfunction,westartbywrappingthefunction'slogicwithapromise,invokethefunction_loadJsonAsync,andassignitsreturnvaluetothevariablegetJson.Ifwereturntothe_loadJsonAsyncfunction,wewillnoticethatthereturntypeisapromise.Therefore,thegetJsonvariableisapromisethatoncefulfilledwillreturntheJSONdatarequiredtorenderourview.

Thistime,wewillinvokethethenmethod,whichbelongstothepromisereturnedbythe_loadHbsAsyncmethod.Thiswillallowustopasstheoutputofthefunction_loadHbsAsyncto_compileHbsAsyncwhenthepromise'sstatechangestofulfilled.

publicrenderAsync(){

returnQ.Promise((resolve,reject)=>{

try

{

//assignpromisetogetJson

vargetJson=this._loadJsonAsync(this._serviceUrl,this._args);

//assignpromisetogetTemplate

vargetTemplate=this._loadHbsAsync(this._templateUrl)

.then(this._compileHbsAsync);

//executepromisesinparallel

Q.all([getJson,getTemplate]).then((results)=>{

varjson=results[0];

vartemplate=results[1];

this._jsonToHtmlAsync(template,json)

.then((html:string)=>{

returnthis._appendHtmlAsync(html,this._container);

})

.then(($container:any)=>{resolve($container);});

});

}

catch(error){

reject(error);

}

});

}

}

OncewehavedeclaredthegetJsonandgetTemplatevariables(eachcontainingapromiseasavalue)wewillusetheallmethodfromtheQlibrarytoexecutethegetJsonandgetTemplatepromisesinparallel.

Q'sallmethodtakesalistofpromisesandacallbackasinput.Onceallthepromisesinthelist

Page 141: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

havebeenfulfilled,thecallbackisinvokedandanarraynamedresultsispassedtothefulfilmentcallback.Thearraycontainstheresultsofeachofthepromisesinthesameorderthattheywerepassedtotheallmethod.

InsideQ'sallmethodcallback,wewillusetheloadedJSONandthecompiledtemplateandargumentswheninvokingthe_jsonToHtmlAsyncpromise.Wewillfinallyusethethenmethodtocallthe_appendHtmlAsyncmethodandresolvethepromise.

Asobservedintheexample,usingpromisesgivesusbettercontrolovertheexecutionflowofeachoftheoperationsinourrendermethod.Rememberthatyoucanusefourdifferenttypesofasynchronousflowcontrol:

Concurrent:Thetasksareexecutedinparallel.WesawthisintheexamplewhenweusedtheallmethodinthegetJsonandgetTemplatepromises.Series:Agroupoftasksisexecutedinsequencebuttheprecedingtasksdonotpassargumentstothenexttask.Waterfall:Agroupoftasksisexecutedinsequenceandeachtaskpassesargumentstothenexttask.Thisapproachisusefulwhenthetaskshavedependenciesoneachother.Intheprecedingexample,wefindthisasynchronousflowcontrolapproachwhenthe_loadHbsAsyncpromisepassesitsoutputtothe_compileHbsAsyncpromise.Composite:Thisisanycombinationofthepreviousconcurrent,series,andwaterfallapproaches.Therendermethodintheexampleusesacombinationofalltheasynchronousflowcontrolapproachesinthislist.

Page 142: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GeneratorsIfweinvokeafunctioninTypeScript,wecanassumethatoncethefunctionstartsrunning,itwillalwaysruntocompletionbeforeanyothercodecanrun.Thishasbeenthecaseuntilnow.However,anewkindoffunctionwhichmaybepausedinthemiddleofexecution—oneormanytimes—andresumedlater,allowingothercodetorunduringthesepausedperiods,isabouttoarriveinTypeScriptandES6.Thesenewkindsoffunctionsareknownasgenerators.

Ageneratorrepresentsasequenceofvalues.Theinterfaceofageneratorobjectisajustaniterator.Thenext()functioncanbeinvokeduntilitrunsoutofvalues.

Wecandefinetheconstructorofageneratorbyusingthefunctionkeywordfollowedbyanasterisk(*).Theyieldkeywordisusedtostoptheexecutionofthefunctionandreturnavalue.Let'stakealookatanexample:

function*foo(){

yield1;

yield2;

yield3;

yield4;

return5;

}

varbar=newfoo();

bar.next();//Object{value:1,done:false}

bar.next();//Object{value:2,done:false}

bar.next();//Object{value:3,done:false}

bar.next();//Object{value:4,done:false}

bar.next();//Object{value:5,done:true}

bar.next();//Object{done:true}

Asyoucansee,thisiteratorhasfivesteps.Thefirsttimewecallnext,thefunctionwillbeexecuteduntilitreachesthefirstyieldstatement,andthenitwillreturnthevalue1andstoptheexecutionofthefunctionuntilweinvokethegenerator'snextmethodagain.Aswecansee,wearenowabletostopthefunction'sexecutionatagivenpoint.Thisallowsustowriteinfiniteloopswithoutcausingastackoverflowasinthefollowingexample:

function*foo(){

vari=1;

while(true){

yieldi++;

}

}

varbar=newfoo();

bar.next();//Object{value:1,done:false}

bar.next();//Object{value:2,done:false}

bar.next();//Object{value:3,done:false}

bar.next();//Object{value:4,done:false}

bar.next();//Object{value:5,done:false}

Page 143: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

bar.next();//Object{value:6,done:false}

bar.next();//Object{value:7,done:false}

//...

Generatorswillopenpossibilitiesforsynchronicityaswecancallagenerator'snextmethodaftersomeasynchronouseventhasoccurred.

Page 144: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Asynchronousfunctions–asyncandawaitAsynchronousfunctionsareaTypeScriptfeaturethatisscheduledtoarrivewiththeupcomingTypeScriptreleases.Anasynchronousfunctionisafunctionthatisexpectedtobeinvokedinasynchronousoperation.Developerscanusetheawaitkeywordtowaitforthefunctionresultswithoutblockingthenormalexecutionoftheprogram.

AsynchronousfunctionswillbeimplementedusingpromiseswhentargetingES6,andpromisefallbackswhentargetingES3andES5.

Usingasynchronousfunctionsgenerallyhelpstoincreasethereadabilityofapieceofcodewhencomparedwiththeuseofpromises;buttechnicallywecanachievethesamefeaturesusingbothpromisesandsynchronouscode.

Let'stakeasneak-peekatthisupcomingfeature:

varp:Promise<number>=/*...*/;

asyncfunctionfn():Promise<number>{

vari=awaitp;

return1+i;

}

Theprecedingcodesnippetdeclaresapromisenamedp.Thispromiseisthepieceofcodethatwillwaittobeexecuted.Whilewaiting,theprogramexecutionwillnotbeblockedbecausewewillwaitfromanasynchronousfunctionnamedfn.Aswecansee,thefnfunctionisprecededbytheasynckeyword,whichisusedtoindicatetothecompilerthatitisanasynchronousfunction.

Insidethefunction,theawaitkeywordisusedtosuspendexecutionuntilpissettled.Aswecansee,thesyntaxismuchmoreminimalisticandcleanerthanitwouldbeifweusedthepromisesAPI(thenandcatchmethodsandcallbacks).

Note

RefertotheTypeScriptroadmaptolearnmoreaboutthestagesofdevelopmentofthisfeature.

Page 145: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wesawhowtoworkwithfunctionsindepth.Westartedwithaquickrecapofsomebasicconceptsandthenmovedtosomelesserknownfunctionfeaturesandusecases.

Oncewesawhowtoworkwithfunctions,wefocusedontheusageofcallbacks,promises,andgeneratorstotakeadvantageoftheasynchronousprogrammingcapabilitiesofTypescript.

Inthenextchapter,wewilllookathowtoworkwithclasses,interfaces,andotherobject-orientedprogrammingfeaturesoftheTypeScriptprogramminglanguage.

Page 146: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter4.Object-OrientedProgrammingwithTypeScriptInthepreviouschapter,weexploredtheuseoffunctionsandsomeasynchronoustechniques.Inthischapter,wewillseehowtogroupourfunctionsinreusablecomponents,suchasclassesormodules.Thischapterisdividedintotwomainsections.Thefirstpartwillcoverthefollowingtopics:

SOLIDprinciplesClassesAssociation,aggregation,andcompositionInheritanceMixinsGenericclassesGenericconstraintsInterfaces

Inthesecondpart,wewillfocusonthedeclarationanduseofnamespacesandexternalmodules.Thesecondpartwillcoverthefollowingtopics:

Namespaces(internalmodules)ExternalmodulesAsynchronousmoduledefinition(AMD)CommonJSmodulesES6modulesBrowserifyanduniversalmoduledefinition(UMD)Circulardependencies

Page 147: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SOLIDprinciplesIntheearlydaysofsoftwaredevelopment,developersusedtowritecodewithproceduralprograminglanguages.Inproceduralprogramminglanguages,theprogramsfollowatop-to-bottomapproachandthelogiciswrappedwithfunctions.

Newstylesofcomputerprogramming,suchasmodularprogrammingorstructuredprogramming,emergedwhendevelopersrealizedthatproceduralcomputerprogramscouldnotprovidethemwiththedesiredlevelofabstraction,maintainability,andreusability.

Thedevelopmentcommunitycreatedaseriesofrecommendedpracticesanddesignpatternstoimprovethelevelofabstractionandreusabilityofproceduralprogramminglanguages,butsomeoftheseguidelinesrequiredacertainlevelofexpertise.Inordertofacilitateadherencetotheseguidelines,anewstyleofcomputerprogrammingknownasobject-orientedprogramming(OOP)wascreated.

DevelopersquicklynoticedsomecommonOOPmistakesandcameupwithfiverulesthateveryOOPdevelopershouldfollowtocreateasystemthatiseasytomaintainandextendovertime.ThesefiverulesareknownastheSOLIDprinciples.SOLIDisanacronymintroducedbyMichaelFeathers,whichstandsforthefollowingprinciples:

Singleresponsibilityprinciple(SRP):Thisprinciplestatesthatasoftwarecomponent(function,class,ormodule)shouldfocusononeuniquetask(haveonlyoneresponsibility).Open/closedprinciple(OCP):Thisprinciplestatesthatsoftwareentitiesshouldbedesignedwithapplicationgrowth(newcode)inmind(shouldbeopentoextension),buttheapplicationgrowthshouldrequirethefewerpossiblenumberofchangestotheexistingcode(beclosedformodification).Liskovsubstitutionprinciple(LSP):Thisprinciplestatesthatweshouldbeabletoreplaceaclassinaprogramwithanotherclassaslongasbothclassesimplementthesameinterface.Afterreplacingtheclass,nootherchangesshouldberequired,andtheprogramshouldcontinuetoworkasitdidoriginally.Interfacesegregationprinciple(ISP):Thisprinciplestatesthatweshouldsplitinterfacesthatareverylarge(general-purposeinterfaces)intosmallerandmorespecificones(manyclient-specificinterfaces)sothatclientswillonlyneedtoknowaboutthemethodsthatareofinteresttothem.Dependencyinversionprinciple(DIP):Thisprinciplestatesthatentitiesshoulddependonabstractions(interfaces)asopposedtodependingonconcretion(classes).

Inthischapter,wewillseehowtowriteTypeScriptcodethatadherestotheseprinciplessothatourapplicationsareeasytomaintainandextendovertime.

Page 148: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ClassesWeshouldalreadybefamiliarwiththebasicsaboutTypeScriptclasses,aswehavedeclaredsomeoftheminpreviouschapters.SowewillnowlookatsomedetailsandOOPconceptsthroughexamples.Let'sstartbydeclaringasimpleclass:

classPerson{

publicname:string;

publicsurname:string;

publicemail:string;

constructor(name:string,surname:string,email:string){

this.email=email;

this.name=name;

this.surname=surname;

}

greet(){

alert("Hi!");

}

}

varme:Person=newPerson("Remo","Jansen","[email protected]");

Weuseclassestorepresentthetypeofanobjectorentity.Aclassiscomposedofaname,attributes,andmethods.TheclassintheprecedingexampleisnamedPersonandcontainsthreeattributesorproperties(name,surname,andemail)andtwomethods(constructorandgreet).Classattributesareusedtodescribetheobject'scharacteristics,whileclassmethodsareusedtodescribeitsbehavior.

Aconstructorisaspecialmethodusedbythenewkeywordtocreateinstances(alsoknownasobjects)ofourclass.Wehavedeclaredavariablenamedme,whichholdsaninstanceofthePersonclass.ThenewkeywordusesthePersonclass'sconstructortoreturnanobjectwhosetypeisPerson.

Aclassshouldadheretothesingleresponsibilityprinciple(SRP).ThePersonclassintheprecedingexamplerepresentsaperson,includingalltheircharacteristics(attributes)andbehaviors(methods).Nowlet'saddsomeemailasvalidationlogic:

classPerson{

publicname:string;

publicsurname:string;

publicemail:string;

constructor(name:string,surname:string,email:string){

this.surname=surname;

this.name=name;

if(this.validateEmail(email)){

this.email=email;

}

else{

thrownewError("Invalidemail!");

}

}

Page 149: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

validateEmail(){

varre=/\S+@\S+\.\S+/;

returnre.test(this.email);

}

greet(){

alert("Hi!I'm"+this.name+".Youcanreachmeat"+this.email);

}

}

Whenanobjectdoesn'tfollowtheSRPanditknowstoomuch(hastoomanyproperties)ordoestoomuch(hastoomanymethods),wesaythattheobjectisaGodobject.ThePersonclasshereisaGodobjectbecausewehaveaddedamethodnamedvalidateEmailthatisnotreallyrelatedtothePersonclass'sbehavior.

Decidingwhichattributesandmethodsshouldorshouldnotbepartofaclassisarelativelysubjectivedecision.Ifwespendsometimeanalyzingouroptions,weshouldbeabletofindawaytoimprovethedesignofourclasses.

WecanrefactorthePersonclassbydeclaringanEmailclass,responsiblefore-mailvalidation,anduseitasanattributeinthePersonclass:

classEmail{

publicemail:string;

constructor(email:string){

if(this.validateEmail(email)){

this.email=email;

}

else{

thrownewError("Invalidemail!");

}

}

validateEmail(email:string){

varre=/\S+@\S+\.\S+/;

returnre.test(email);

}

}

NowthatwehaveanEmailclass,wecanremovetheresponsibilityofvalidatingtheemailsfromthePersonclassandupdateitsemailattributetousethetypeEmailinsteadofstring:

classPerson{

publicname:string;

publicsurname:string;

publicemail:Email;

constructor(name:string,surname:string,email:Email){

this.email=email;

this.name=name;

this.surname=surname;

}

greet(){

alert("Hi!");

}

}

Page 150: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Makingsurethataclasshasasingleresponsibilitymakesiteasiertoseewhatitdoesandhowwecanextend/improveit.WecanfurtherimproveourPersonandEmailclassesbyincreasingthelevelofabstractionofourclasses.Forexample,whenweusetheEmailclass,wedon'treallyneedtobeawareoftheexistenceofthevalidateEmailmethod;sothismethodcouldbeinvisiblefromoutsidetheEmailclass.Asaresult,theEmailclasswouldbemuchsimplertounderstand.

Whenweincreasethelevelofabstractionofanobject,wecansaythatweareencapsulatingtheobject'sdataandbehavior.Encapsulationisalsoknownasinformationhiding.Forexample,theEmailclassallowsustouseemailswithouthavingtoworryaboute-mailvalidationbecausetheclasswilldealwithitforus.Wecanmakethisclearerbyusingaccessmodifiers(publicorprivate)toflagasprivatealltheclassattributesandmethodsthatwewanttoabstractfromtheuseoftheEmailclass:

classEmail{

privateemail:string;

constructor(email:string){

if(this.validateEmail(email)){

this.email=email;

}

else{

thrownewError("Invalidemail!");

}

}

privatevalidateEmail(email:string){

varre=/\S+@\S+\.\S+/;

returnre.test(email);

}

get():string{

returnthis.email;

}

}

WecanthensimplyusetheEmailclasswithoutneedingtoexplicitlyperformanykindofvalidation:

varemail=newEmail("[email protected]");

Page 151: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InterfacesThefeaturethatwewillmissthemostwhendevelopinglarge-scalewebapplicationswithJavaScriptisprobablyinterfaces.WehaveseenthatfollowingtheSOLIDprinciplescanhelpustoimprovethequalityofourcode,andwritinggoodcodeisamustwhenworkingonalargeproject.TheproblemisthatifweattempttofollowtheSOLIDprincipleswithJavaScript,wewillsoonrealizethatwithoutinterfaces,wewillneverbeabletowriteSOLIDOOPcode.Fortunatelyforus,TypeScriptfeaturesinterfaces.

Traditionally,inOOP,wesaythataclasscanextendanotherclassandimplementoneormoreinterfaces.Aninterfacecanimplementoneormoreinterfacesandcannotextendanotherclassorinterface.Wikipedia'sdefinitionofinterfacesinOOPisasfollows:

Inobject-orientedlanguages,theterminterfaceisoftenusedtodefineanabstracttypethatcontainsnodataorcode,butdefinesbehaviorsasmethodsignatures.

Implementinganinterfacecanbeunderstoodassigningacontract.Theinterfaceisacontract,andwhenwesignit(implementit),wemustfollowitsrules.Theinterfacerulesarethesignaturesofthemethodsandproperties,andwemustimplementthem.

Wewillseemanyexamplesofinterfaceslaterinthischapter.

InTypeScript,interfacesdon'tstrictlyfollowthisdefinition.ThetwomaindifferencesarethatinTypeScript:

AninterfacecanextendanotherinterfaceorclassAninterfacecandefinedataandbehaviorsasopposedtoonlybehaviors

Page 152: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Association,aggregation,andcompositionInOOP,classescanhavesomekindofrelationshipwitheachother.Now,wewilltakealookatthethreedifferenttypesofrelationshipsbetweenclasses.

Page 153: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AssociationWecallassociationthoserelationshipswhoseobjectshaveanindependentlifecycleandwherethereisnoownershipbetweentheobjects.Let'stakeanexampleofateacherandstudent.Multiplestudentscanassociatewithasingleteacher,andasinglestudentcanassociatewithmultipleteachers,butbothhavetheirownlifecycles(bothcanbecreateanddeleteindependently);sowhenateacherleavestheschool,wedon'tneedtodeleteanystudents,andwhenastudentleavestheschool,wedon'tneedtodeleteanyteachers.

Page 154: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AggregationWecallaggregationthoserelationshipswhoseobjectshaveanindependentlifecycle,butthereisownership,andchildobjectscannotbelongtoanotherparentobject.Let'stakeanexampleofacellphoneandacellphonebattery.Asinglebatterycanbelongtoaphone,butifthephonestopsworking,andwedeleteitfromourdatabase,thephonebatterywillnotbedeletedbecauseitmaystillbefunctional.Soinaggregation,whilethereisownership,objectshavetheirownlifecycle.

Page 155: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CompositionWeusethetermcompositiontorefertorelationshipswhoseobjectsdon'thaveanindependentlifecycle,andiftheparentobjectisdeleted,allchildobjectswillalsobedeleted.

Let'stakeanexampleoftherelationshipbetweenquestionsandanswers.Singlequestionscanhavemultipleanswers,andanswerscannotbelongtomultiplequestions.Ifwedeletequestions,answerswillautomaticallybedeleted.

Objectswithadependentlifecycle(answers,intheexample)areknownasweakentities.

Sometimes,itcanbeacomplicatedprocesstodecideifweshoulduseassociation,aggregation,orcomposition.Thisdifficultyiscausedinpartbecauseaggregationandcompositionaresubsetsofassociation,meaningtheyarespecificcasesofassociation.

Page 156: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InheritanceOneofthemostfundamentalobject-orientedprogrammingfeaturesisitscapabilitytoextendexistingclasses.Thisfeatureisknownasinheritanceandallowsustocreateanewclass(childclass)thatinheritsallthepropertiesandmethodsfromanexistingclass(parentclass).Childclassescanincludeadditionalpropertiesandmethodsnotavailableintheparentclass.Let'sreturntoourpreviouslydeclaredPersonclass.WewillusethePersonclassastheparentclassofachildclassnamedTeacher:

classPerson{

publicname:string;

publicsurname:string;

publicemail:Email;

constructor(name:string,surname:string,email:Email){

this.name=name;

this.surname=surname;

this.email=email;

}

greet(){

alert("Hi!");

}

}

Note

Thisexampleisincludedinthecompanionsourcecode.

Oncewehaveaparentclassinplace,wecanextenditbyusingthereservedkeywordextends.Inthefollowingexample,wedeclareaclasscalledTeacher,whichextendsthepreviouslydefinedPersonclass.ThismeansthatTeacherwillinheritalltheattributesandmethodsfromitsparentclass:

classTeacherextendsPerson{

teach(){

alert("Welcometoclass!");

}

}

NotethatwehavealsoaddedanewmethodnamedteachtotheclassTeacher.IfwecreateinstancesofthePersonandTeacherclasses,wewillbeabletoseethatbothinstancessharethesameattributesandmethodswiththeexceptionoftheteachmethod,whichisonlyavailablefortheinstanceoftheTeacherclass:

varteacher=newTeacher("remo","jansen",new

Email("[email protected]"));

varme=newPerson("remo","jansen",newEmail("[email protected]"));

me.greet();

teacher.greet();

me.teach();//Error:Property'teach'doesnotexistontype'Person'

Page 157: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

teacher.teach();

Sometimes,wewillneedachildclasstoprovideaspecificimplementationofamethodthatisalreadyprovidedbyitsparentclass.Wecanusethereservedkeywordsuperforthispurpose.Imaginethatwewanttoaddanewattributetolisttheteacher'ssubjects,andwewanttobeabletoinitializethisattributethroughtheteacherconstructor.Wewillusethesuperkeywordtoexplicitlyreferencetheparentclassconstructorinsidethechildclassconstructor.Wecanalsousethesuperkeywordwhenwewanttoextendanexistingmethod,suchasgreet.ThisOOPlanguagefeaturethatallowsasubclassorchildclasstoprovideaspecificimplementationofamethodthatisalreadyprovidedbyitsparentclassesisknownasmethodoverriding.

classTeacherextendsPerson{

publicsubjects:string[];

constructor(name:string,surname:string,email:Email,subjects:

string[]){

super(name,surname,email);

this.subjects=subjects;

}

greet(){

super.greet();

alert("Iteach"+this.subjects);

}

teach(){

alert("WelcometoMathsclass!");

}

}

varteacher=newTeacher("remo","jansen",new

Email("[email protected]"),["math","physics"]);

Wecandeclareanewclassthatinheritsfromaclassthatisalreadyinheritingfromanother.Inthefollowingcodesnippet,wedeclareaclasscalledSchoolPrincipalthatextendstheTeacherclass,whichextendsthePersonclass:

classSchoolPrincipalextendsTeacher{

manageTeachers(){

alert("Weneedtohelpstudentstogetbetterresults!");

}

}

IfwecreateaninstanceoftheSchoolPrincipalclass,wewillbeabletoaccessallthepropertiesandmethodsfromitsparentclasses(SchoolPrincipal,Teacher,andPerson):

varprincipal=newSchoolPrincipal("remo","jansen",new

Email("[email protected]"),["math","physics"]);

principal.greet();

principal.teach();

principal.manageTeachers();

Itisnotrecommendedtohavetoomanylevelsintheinheritancetree.Aclasssituatedtoodeeplyintheinheritancetreewillberelativelycomplextodevelop,test,andmaintain.Unfortunately,wedon'thaveaspecificrulethatwecanfollowwhenweareunsurewhether

Page 158: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

weshouldincreasethedepthofinheritancetree(DIT).

Weshoulduseinheritanceinsuchawaythatithelpsustoreducethecomplexityofourapplicationandnottheopposite.WeshouldtrytokeeptheDITbetween0and4becauseavaluegreaterthan4wouldcompromiseencapsulationandincreasecomplexity.

Page 159: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MixinsSometimes,wewillfindscenariosinwhichitwouldbeagoodideatodeclareaclassthatinheritsfromtwoormoreclassessimultaneously(knownasmultipleinheritance).

Let'stakealookatanexample.Wewillnotaddanycodetothemethodsinthisexamplebecausewewanttoavoidthepossibilityofgettingdistractedbyit;weshouldfocusontheinheritancetree:

classAnimal{

eat(){

//...

}

}

WestartedbydeclaringaclassnamedAnimal,whichonlyhasonemethodnamedeat.Now,let'sdeclaretwonewclasses:

classMammalextendsAnimal{

breathe(){

//...

}

}

classWingedAnimalextendsAnimal{

fly(){

//...

}

}

WehavedeclaredtwonewclassesnamedWingedAnimalandMammal.BothclassesinheritfromtheAnimalclass.

Nowthatwehaveourclassesready,wearegoingtotrytoimplementaclassnamedBat.Batsaremammalsandhavewings—creatinganewclassnamedBat,whichwillextendboththeMammalandWingedAnimalclasses,seemslogical.However,ifweattempttodoso,wewillencounteracompilationerror:

//Error:Classescanonlyextendasingleclass.

classBatextendsWingedAnimal,Mammal{

//...

}

ThiserroristhrownbecauseTypeScriptdoesn'tsupportmultipleinheritance.Thismeansthataclasscanonlyextendoneclass.ThedesignersofprogramminglanguagessuchasC#orTypeScriptdecidedtonotsupportmultipleinheritancebecauseitcanpotentiallyincreasethecomplexityofapplications.

Sometimes,aclassinheritancediagramcantakeadiamond-likeshape(asseeninthefollowingfigure).Thiskindofclassinheritancediagramcanpotentiallyleadustodesign

Page 160: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

issueknownasthediamondproblem.

Wewillnotfaceanyproblemsifwecallamethodthatisexclusivetoonlyoneoftheclassesintheinheritancetree:

varbat=newBat();

bat.fly();

bat.eat();

bat.breathe();

ThediamondproblemtakesplacewhenwetrytoinvokeoneoftheBatclass'sparent'smethods,anditisunclearorambiguouswhichoftheparent'simplementationsofthatmethodshouldbeinvoked.IfweaddamethodnamedmovetoboththeMammalandtheWingedAnimalclassandtrytoinvokeitfromaninstanceofBat,wewillgetanambiguouscallerror.

Nowthatweknowwhymultipleinheritancecanbepotentiallydangerous,wewillintroduceafeatureknownasmixin.Mixinsarealternativestomultipleinheritance,butthisfeaturehassomelimitations.

Let'sreturntotheBatclassexampletoshowcasetheusageofmixins:

classMammal{

Page 161: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

breathe():string{

return"I'malive!";

}

}

classWingedAnimal{

fly():string{

return"Icanfly!";

}

}

Note

Thisexampleisincludedinthecompanionsourcecode.

Thetwoclassespresentedintheprecedingexamplearenotmuchdifferentfromthepreviousexample;wehaveaddedsomelogictothebreatheandflymethods,sowecanhavesomeoutputtohelpustounderstandthisdemonstration.Also,notethattheclassesnolongerextendtheAnimalclass:

classBatimplementsMammal,WingedAnimal{

breathe:()=>string;

fly:()=>string;

}

TheBatclasshassomeimportantadditions.Wehaveusedthereservedkeywordimplements(asopposedtoextends)toindicatethatBatwillimplementthefunctionalitydeclaredinboththeMammalandWingedAnimalclasses.WehavealsoaddedthesignatureofeachofthemethodsthattheBatclasswillimplement.

Weneedtocopythefollowingfunctionsomewhereinourcodetobeabletoapplymixins:

functionapplyMixins(derivedCtor:any,baseCtors:any[]){

baseCtors.forEach(baseCtor=>{

Object.getOwnPropertyNames(baseCtor.prototype).forEach(name=>{

if(name!=='constructor'){

derivedCtor.prototype[name]=baseCtor.prototype[name];

}

});

});

}

Note

Theprecedingfunctionisawell-knownpatternandcanbefoundinmanybooksandonlinereferences,includingtheofficialTypeScripthandbook.

Thisfunctionwilliterateeachpropertyoftheparentclasses(containedinanarraynamedbaseCtors)andcopytheimplementationtoachildclass(derivedCtor).

Weonlyneedtodeclarethisfunctiononceinourapplication.Oncewehavedoneit,wecan

Page 162: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

useitasfollows:

applyMixins(Bat,[Mammal,WingedAnimal]);

Thechildclass(Bat)willthencontaintheimplementationofeachpropertyandmethodofthetwoparentclasses(WingedAnimalandMammal):

varbat=newBat();

bat.breathe();//I'malive!

bat.fly();//Icanfly!

Aswesaidatthebeginningofthissection,mixinshavesomelimitations.Thefirstlimitationisthatwecanonlyinheritthepropertiesandmethodsfromonelevelintheinheritancetree.NowwecanunderstandwhyweremovedtheAnimalclasspriortoapplyingthemixin.Thesecondlimitationisthat,iftwoormoreoftheparentclassescontainamethodwiththesamename,themethodthatisgoingtobeinheritedwillbetakenfromthelastclasspassedinthebaseCtorsarraytotheapplyMixinsfunction.Wewillnowseeanexamplethatpresentsboththeselimitations.

Inordertoshowthefirstlimitation,wewilldeclaretheAnimalclass:

classAnimal{

eat():string{

return"Delicious!";

}

}

WewillthendeclaretheMammalandWingedAnimalclasses,butthistime,theywillextendtheAnimalclass:

classMammalextendsAnimal{

breathe():string{

return"I'malive!";

}

move():string{

return"Icanmovelikeamammal!";

}

}

classWingedAnimalextendsAnimal{

fly():string{

return"Icanfly!";

}

move():string{

return"Icanmovelikeabird!";

}

}

WewillthendeclareaBatclassbutwewillnameitBat1.ThisclasswillimplementboththeMammalandWindgedAnimalclasses:

classBat1implementsMammal,WingedAnimal{

eat:()=>string;

Page 163: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

breathe:()=>string;

fly:()=>string;

move:()=>string;

}

WearereadytoinvoketheapplyMixinsfunction.NoticehowwepassMammalbeforeWingedAnimalinthearray:

applyMixins(Bat1,[Mammal,WingedAnimal]);

WecannowcreateaninstanceofBat1,andwewillbeabletoobservethattheeatmethodhasnotbeeninheritedfromtheAnimalclassduetothefirstlimitation:

varbat1=newBat();

bat1.eat();//Error:notafunction

Eachoftheparentclass'smethodshasbeeninheritedwithoutissues:

bat1.breathe();//I'malive!

bat1.fly();//Icanfly!"

Exceptthemovemethodbecauseaccordingtothesecondlimitation,onlytheimplementationofthelastparentclasspassedtotheapplyMixinsmethodwillbeimplemented.Inthiscase,theimplementationisinheritedfromtheWingedAnimalclass:

bat1.move();//Icanmovelikeabird

Tofinalize,wewillseetheeffectofswitchingtheorderoftheparentclasseswheninvokingtheapplyMixinsmethod:

classBat2implementsWingedAnimal,Mammal{

eat:()=>string;

breathe:()=>string;

fly:()=>string;

move:()=>string;

}

NoticehowwehavepassedWingedAnimalbeforeMammalinthearray:

applyMixins(Bat2,[WingedAnimal,Mammal]);

varbat2=newBat2();

bat2.eat();//Error:notafunction

bat2.breathe();//I'malive!

bat2.fly();//Icanfly!

bat2.move()//Icanmovelikeamammal

Page 164: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GenericclassesInthepreviouschapter,wesawhowtoworkwithgenericfunctions.Wewillnowtakealookathowtoworkwithgenericclasses.

Justlikewithgenericfunctions,genericclassescanhelpustoavoidtheduplicationofcode.Let'stakealookatanexample:

classUser{

publicname:string;

publicpassword:string;

}

Note

Thisexampleisincludedinthecompanionsourcecode.

WehavedeclaredaUserclass,whichcontainstwoproperties:nameandpassword.WewillnowdeclareaclassnamedNotGenericUserRepositorywithoutusinggenerics.ThisclasstakesaURLviaitsconstructorandhasamethodnamedgetAsync.ThegetAsyncmethodwillrequestalistofusersstoredinaJSONfileusingAJAX:

classNotGenericUserRepository{

private_url:string;

constructor(url:string){

this._url=url;

}

publicgetAsync(){

returnQ.Promise((resolve:(users:User[])=>void,reject)=>{

$.ajax({

url:this._url,

type:"GET",

dataType:"json",

success:(data)=>{

varusers=<User[]>data.items;

resolve(users);

},

error:(e)=>{

reject(e);

}

});

});

}

}

OncewehavefinisheddeclaringtheNotGenericUserRepositoryuserrepository,wecancreateaninstanceandinvokethegetAsyncmethod:

varnotGenericUserRepository=new

NotGenericUserRepository("./demos/shared/users.json");

notGenericUserRepository.getAsync()

.then(function(users:User[]){

Page 165: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

console.log('notGenericUserRepository=>',users);

});

IfwealsoneedtorequestanotherlistofentitiesdifferentfromUser,wecouldendupduplicatingalotofcode.Imaginethatwealsoneedtorequestalistofconferencetalks.WecouldcreateanentitynamedTalkandanalmostidenticalrepositoryclass:

classTalk{

publictitle:string;

publicdescription:string;

publiclanguage:string;

publicurl:string;

publicyear:string;

}

classNotGenericTalkRepository{

private_url:string;

constructor(url:string){

this._url=url;

}

publicgetAsync(){

returnQ.Promise((resolve:(talks:Talk[])=>void,reject)=>{

$.ajax({

url:this._url,

type:"GET",

dataType:"json",

success:(data)=>{

varuserstalks=<Talk[]>data.items;

resolve(userstalks);

},

error:(e)=>{

reject(e);

}

});

});

}

}

Ifthenumberofentitiesgrows,wewillcontinuetorepeatedlyduplicatecode.Wemaythinkthatwecouldusetheanytypetoavoidthisproblem,butthenwewouldbelosingthesecurityprovidedbythecheckingtypeperformedbyTypeScriptatcompilationtime.AmuchbettersolutionistocreateaGenericrepository:

classGenericRepository<T>{

private_url:string;

constructor(url:string){

this._url=url;

}

publicgetAsync(){

returnQ.Promise((resolve:(entities:T[])=>void,reject)=>{

$.ajax({

url:this._url,

type:"GET",

dataType:"json",

Page 166: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

success:(data)=>{

varlist=<T[]>data.items;

resolve(list);

},

error:(e)=>{

reject(e);

}

});

});

}

}

TherepositorycodeisidenticaltoNotGenericUserRepository,exceptfortheentitytype.WehaveremovedthehardcodedreferencetotheUserandTalkentitiesandreplacedthemwiththegenerictypeT.Wecannowdeclareasmanyrepositoriesaswewishwithoutduplicatingasinglelineofcode:

varuserRepository=newGenericRepository<User>("./demos/shared/users.json");

userRepository.getAsync()

.then((users:User[])=>{

console.log('userRepository=>',users);

});

vartalkRepository=newGenericRepository<Talk>("./demos/shared/talks.json");

talkRepository.getAsync()

.then((talks:Talk[])=>{

console.log('talkRepository=>',talks);

});

Page 167: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GenericconstraintsSometimes,wemightneedtorestricttheuseofagenericclass.Takethegenericrepositoryfromtheprevioussectionasanexample.Wehaveanewrequirement:weneedtoaddsomechangestovalidatetheentitiesloadedviaAJAX,andwewillreturnonlythevalidentities.

OnepossiblesolutionistousethetypeofoperatortoidentifythetypeofthegenerictypeparameterTwithinagenericclassorfunction:

//...

success:(data)=>{

varlist:T[];

varitems=<T[]>data.items;

for(vari=0;i<items.length;i++){

if(items[i]instanceofUser){

//validateuser

}

if(items[i]instanceofTalk){

//validatetalk

}

}

resolve(list);

}

//...

TheproblemisthatwewillhavetomodifyourGenericRepositoryclasstoaddextralogicwitheachnewentity.WewillnotaddthevalidationrulesintotheGenericRepositoryrepositoryclassbecauseagenericclassshouldnotbeawareofthetypeusedasthegenerictype.

AbettersolutionistoaddamethodnamedisValidtotheentities,whichwillreturntrueiftheentityisvalid:

//...

success:(data)=>{

varlist:T[];

varitems=<T[]>data.items;

for(vari=0;i<items.length;i++){

if(items[i].isValid()){//error

//...

}

}

resolve(list);

}

//...

ThesecondapproachfollowsthesecondSOLIDprinciple,theopen/closeprinciple,aswecancreatenewentitiesandthegenericrepositorywillcontinuetowork(openforextension),butnoadditionalchangestoitwillberequired(closedformodification).Theonlyproblemwiththisapproachisthat,ifweattempttoinvokeanentity'sisValidmethodinsidethegeneric

Page 168: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

repository,wewillgetacompilationerror.

Theerroristhrownbecauseweareallowedtousethegenericrepositorywithanytype,butnotalltypeshaveamethodnamedisValid.Fortunately,thisissuecaneasilyberesolvedbyusingagenericconstraint.ConstraintswillrestrictthetypesthatweareallowedtouseasthegenerictypeparameterT.Wearegoingtodeclareaconstraint,soonlytypesthatimplementaninterfacenamedValidatableInterfacecanbeusedwiththegenericmethod.

Let'sstartbydeclaringaninterface:

interfaceValidatableInterface{

isValid():boolean;

}

Note

Thisexampleisincludedinthecompanionsourcecode.

Nowwecanproceedtoimplementtheinterface.Inthiscase,wemustimplementtheisValidmethod:

classUserimplementsValidatableInterface{

publicname:string;

publicpassword:string;

publicisValid():boolean{

//uservalidation...

returntrue;

}

}

classTalkimplementsValidatableInterface{

publictitle:string;

publicdescription:string;

publiclanguage:string;

publicurl:string;

publicyear:string;

publicisValid():boolean{

//talkvalidation...

returntrue;

}

}

Now,let'sdeclareagenericrepositoryandaddatypeconstraintsoonlytypesderivedfromValidatableInterfacewillbeacceptedasthegenerictypeparameterT:

classGenericRepositoryWithConstraint<TextendsValidatableInterface>{

private_url:string;

constructor(url:string){

this._url=url;

}

publicgetAsync(){

returnQ.Promise((resolve:(talks:T[])=>void,reject)=>{

$.ajax({

Page 169: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

url:this._url,

type:"GET",

dataType:"json",

success:(data)=>{

varitems=<T[]>data.items;

for(vari=0;i<items.length;i++){

if(items[i].isValid()){

list.push(items[i]);

}

}

resolve(list);

},

error:(e)=>{

reject(e);

}

});

});

}

}

Note

Eventhoughwehaveusedaninterface,weusedtheextendskeywordandnottheimplementskeywordtodeclaretheconstraintintheprecedingexample.Thereisnospecialreasonforthat.ThisisjustthewaytheTypeScriptconstraintsyntaxworks.

Wecanthencreateasmanyrepositoriesaswewant:

varuserRepository=new

GenericRepositoryWithConstraint<User>("./users.json");

userRepository.getAsync()

.then(function(users:User[]){

console.log(users);

});

vartalkRepository=new

GenericRepositoryWithConstraint<Talk>("./talks.json");

talkRepository.getAsync()

.then(function(talks:Talk[]){

console.log(talks);

});

Ifweattempttouseaclassthatdoesn'timplementtheValidatableInterfaceofthegenerictypeparameterT,wewillgetacompilationerror.

Page 170: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MultipletypesingenerictypeconstraintsWecanonlyrefertoonetypewhendeclaringagenerictypeconstraint.Let'simaginethatweneedagenericclasstobeconstrained,soitonlyallowstypesthatimplementthefollowingtwointerfaces:

interfaceIMyInterface{

doSomething();

};

interfaceIMySecondInterface{

doSomethingElse();

};

Wemaythinkthatwecandefinetherequiredgenericconstraintasfollows:

classExample<TextendsIMyInterface,IMySecondInterface>{

privategenericProperty:T;

useT(){

this.genericProperty.doSomething();

this.genericProperty.doSomethingElse();//error

}

}

However,thiscodesnippetwillthrowacompilationerror.Wecannotspecifymultipletypeswhendeclaringagenerictypeconstraint.However,wecanworkaroundthisissuebytransformingIMyInterface,IMySecondInterfaceinsuper-interfaces:

interfaceIChildInterfaceextendsIMyInterface,IMySecondInterface{

}

IMyInterfaceandIMySecondInterfacearenowsuper-interfacesbecausetheyaretheparentinterfacesoftheIChildInterfaceinterface.WecanthendeclaretheconstraintusingtheIChildInterfaceinterface:

classExample<TextendsIChildInterface>{

privategenericProperty:T;

useT(){

this.genericProperty.doSomething();

this.genericProperty.doSomethingElse();

}

}

Page 171: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThenewoperatoringenerictypesTocreateanewobjectwithingenericcode,weneedtoindicatethatthegenerictypeThasaconstructorfunction.Thismeansthatinsteadofusingtype:T,weshouldusetype:{new():T;}asfollows:

functionfactoryNotWorking<T>():T{

returnnewT();//compileerrorcouldnotfindsymbolT

}

functionfactory<T>():T{

vartype:{new():T;};

returnnewtype();

}

varmyClass:MyClass=factory<MyClass>();

Page 172: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ApplyingtheSOLIDprinciplesAswehavepreviouslymentioned,interfacesarefundamentalfeatureswhenitcomestofollowingtheSOLIDprinciples,andwehavealreadyputthefirsttwoSOLIDprinciplesintopractice.

Wehavealreadydiscussedthesingleresponsibilityprinciple.Now,wewillseerealexamplesofthethreeremainingprinciples.

Page 173: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheLiskovsubstitutionprincipleTheLiskovsubstitutionprinciple(LSP)states,Subtypesmustbesubstitutablefortheirbasetypes.Let'stakealookatanexampletounderstandwhatthismeans.

WewilldeclareaclassnamedPersistanceService,theresponsibilityofwhichistopersistsomeobjectintosomesortofstorage.Wewillstartbydeclaringthefollowinginterface:

interfacePersistanceServiceInterface{

save(entity:any):number;

}

AfterdeclaringthePersistanceServiceInterfaceinterface,wecanimplementit.Wewillusecookiesasthestoragefortheapplication'sdata:

classCookiePersitanceServiceimplementsPersistanceServiceInterface{

save(entity:any):number{

varid=Math.floor((Math.random()*100)+1);

//Cookiepersistancelogic...

returnid;

}

}

WewillcontinuebydeclaringaclassnamedFavouritesController,whichhasadependencyonPersistanceServiceInterface:

classFavouritesController{

private_persistanceService:PersistanceServiceInterface;

constructor(persistanceService:PersistanceServiceInterface){

this._persistanceService=persistanceService;

}

publicsaveAsFavourite(articleId:number){

returnthis._persistanceService.save(articleId);

}

}

WecanfinallycreateaninstanceofFavouritesControllerandpassaninstanceofCookiePersitanceServiceviaitsconstructor:

varfavController=newFavouritesController(newCookiePersitanceService());

TheLSPallowsustoreplaceadependencywithanotherimplementationaslongasbothimplementationsarebasedinthesamebasetype;so,ifwedecidetostopusingcookiesasstorageandusetheHTML5localstorageAPIinstead,wecandeclareanewimplementation:

classLocalStoragePersitanceServiceimplementsPersistanceServiceInterface{

save(entity:any):number{

varid=Math.floor((Math.random()*100)+1);

//Localstoragepersistancelogic...

returnid;

}

}

Page 174: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WecanthenreplaceitwithouthavingtoaddanychangestotheFavouritesControllercontrollerclass.

varfavController=newFavouritesController(new

LocalStoragePersitanceService());

Page 175: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheinterfacesegregationprincipleInterfacesareusedtodeclarehowtwoormoresoftwarecomponentscooperateandexchangeinformationwitheachother.Thisdeclarationisknownasapplicationprogramminginterface(API).Inthepreviousexample,ourinterfacewasPersistanceServiceInterface,anditwasimplementedbytheclassesLocalStoragePersitanceServiceandCookiePersitanceService.TheinterfacewasconsumedbytheFavouritesControllerclass;sowesaythatthisclassisaclientofthePersistanceServiceInterface'sAPI.

Theinterfacesegregationprinciple(ISP)statesthatnoclientshouldbeforcedtodependonmethodsitdoesnotuse.ToadheretotheISP,weneedtokeepinmindthatwhenwedeclaretheAPI(howtwoormoresoftwarecomponentscooperateandexchangeinformationwitheachother)ofourapplication'scomponents,thedeclarationofmanyclient-specificinterfacesisbetterthanthedeclarationofonegeneral-purposeinterface.Let'stakealookatanexample.

IfwedesignanAPItocontrolalltheelementsinavehicle(engine,radio,heating,navigation,lights…),wecouldhaveonegeneral-purposeinterface,whichallowsustocontroleverysingleelementofthevehicle:

interfaceVehicleInterface{

getSpeed():number;

getVehicleType:string;

isTaxPayed():boolean;

isLightsOn():boolean;

isLightsOff():boolean;

startEngine():void;

acelerate():number;

stopEngine():void;

startRadio():void;

playCd:void;

stopRadio():void;

}

Note

Thisexampleisincludedinthecompanionsourcecode.

Ifaclasshasadependency(client)intheVehicleInterfaceinterfacebutitonlywantstousetheradiomethods,wewillbefacingaviolationoftheISPbecause,aswehavealreadyseen,noclientshouldbeforcedtodependonmethodsitdoesnotuse.

ThesolutionistosplittheVehicleInterfaceinterfaceintomanyclient-specificinterfacessothatourclasscanadheretotheISPbydependingonlyontheRadioInterfaceinterface:

interfaceVehicleInterface{

getSpeed():number;

getVehicleType:string;

isTaxPayed():boolean;

isLightsOn():boolean;

}

Page 176: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

interfaceLightsInterface{

isLightsOn():boolean;

isLightsOff():boolean;

}

interfaceRadioInterface{

startRadio():void;

playCd:void;

stopRadio():void;

}

interfaceEngineInterface{

startEngine():void;

acelerate():number;

stopEngine():void;

}

Page 177: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThedependencyinversionprincipleThedependencyinversion(DI)principlestates,Dependuponabstractions.Donotdependuponconcretions.Intheprevioussection,weimplementedFavouritesControllerandwewereabletoreplaceanimplementationofPersistanceServiceInterfacewithanotherwithouthavingtoperformanyadditionalchangetoFavouritesController.ThiswaspossiblebecausewefollowedtheDIprinciple,asFavouritesControllerhasadependencyuponPersistanceServiceInterface(abstractions)ratherthanLocalStoragePersitanceServiceorCookiePersitanceService(concretions).

Note

Dependingonyourbackground,youmaywonderifthereareanyInversionofControl(IoC)containersavailableforTypeScript.WecanindeedfindsomeIoCcontainersavailableonline.However,becauseTypescript'sruntimedoesn'tsupportreflectionorinterfaces,theycanarguablybeconsideredpseudoIoCcontainersratherthanrealIoCcontainers.

Ifyouwanttolearnmoreaboutinversionofcontrol,Ihighlyrecommendthearticle,InversionofControlContainersandtheDependencyInjectionpattern,byMartinFowler,availableathttp://martinfowler.com/articles/injection.html.

Page 178: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NamespacesTypeScriptfeaturesnamespaces(previouslyknownasinternalmodules).Namespacesaremainlyusedtoorganizeourcode.

Ifweareworkingonalargeapplication,asthecodebasegrowswewillneedtointroducesomekindoforganizationschemetoavoidnamingcollisionsandmakeourcodeeasiertofollowandunderstand.

Wecanusenamespacestoencapsulateinterfaces,classes,andobjectsthataresomehowrelated.Forexample,wecouldwrapallourapplicationmodelsinsideaninternalmodulenamedmodel:

namespaceapp{

exportclassUserModel{

//...

}

}

Whenwedeclareanamespace,allitsentitiesareprivatebydefault.Wecanusetheexportkeywordtodeclarewhatpartsofournamespacewewishtomakepublic.

Weareallowedtonestanamespaceinsideanother.Let'screateafilenamedmodels.tsandaddthefollowingcodesnippettoit:

namespaceapp{

exportnamespacemodels{

exportclassUserModel{

//...

}

exportclassTalkModel{

//...

}

}

}

Intheprecedingexample,wehavedeclaredanamespacenamedapp,andinsideit,wehavedeclaredapublicnamespacenamedmodels,whichcontainstwopublicclasses:UserModelandTalkModel.WecanthencallthenamespacefromanotherTypeScriptfilebyindicatingthefullnamespacename:

varuser=newapp.models.UserModel();

vartalk=newapp.models.TalkModel();

Ifaninternalmodulebecomestoobig,itcanbedividedintomultiplefilestoincreaseitsmaintainability.Ifwetaketheprecedingexample,wecouldaddmorecontentstotheinternalmodulenamedappbyreferencingitinanotherfile.

Page 179: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Let'screateanewfilenamedvalidation.tsandaddthefollowingcodetoit:

namespaceapp{

exportnamespacevalidation{

exportclassUserValidator{

//...

}

exportclassTalkValidator{

//...

}

}

}

Let'screateafilenamedmain.tsandaddthefollowingcodetoit:

varuser=newapp.models.UserModel();

vartalk=newapp.models.TalkModel();

varuserValidator=newapp.validation.UserValidator();

vartalkValidator=newapp.validation.TalkValidator();

Eventhoughthenamespaces'modelsandvalidationareintwodifferentfiles,weareabletoaccessthemfromathirdfile.

Namespacecancontainperiods.Forexample,insteadofnestingthenamespaces(validationandmodels)insidetheappmodule,wecouldhaveusedperiodsinthevalidationandmodelinternalmodulenames:

namespaceapp.validation{

//...

}

namespaceapp.models{

//...

}

Theimportkeywordcanbeusedwithinaninternalmoduletoprovideanaliasforanothermodule:

importTalkValidatorAlias=app.validation.TalkValidator;

vartalkValidator=newTalkValidatorAlias();

Oncewehavefinisheddeclaringournamespaces,wecandecideifwewanttocompileeachoneintoJavaScriptorifweprefertoconcatenateallthefilesintoonesinglefile.

Wecanusethe--outflagtocompilealltheinputfilesintoasingleJavaScriptoutputfile:

tsc--outoutput.jsinput.ts

Thecompilerwillautomaticallyordertheoutputfilebasedonthereferencetagspresentinthefiles.WecanthenimportourfilesorfileusinganHTML<script>tag.

Page 180: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ModulesTypeScriptalsohastheconceptofexternalmodulesorjustmodules.Themaindifferencebetweenusingmodules(insteadofnamespaces)isthatafterdeclaringallourmodules,wewillnotimportthemusinganHTML<script>tagandwewillbeabletouseamoduleloaderinstead.

Amoduleloaderisatoolthatallowsustohavebettercontroloverthemoduleloadingprocess.Thisallowsustoperformtaskssuchasloadingfilesasynchronouslyorcombiningmultiplemodulesintoasinglehighlyoptimizedfilewithease.

Usingthe<script>tagisnotrecommendedbecausewhenawebbrowserfindsa<script>tag,itdownloadsthefileusingasynchronousrequests.Weshouldattempttoloadasmanyfilesaspossibleusingasynchronousrequestsbecausedoingsowillsignificantlyimprovethenetworkperformanceofawebapplication.

Note

WewilldiscovermoreaboutnetworkperformanceinChapter6,ApplicationPerformance.

TheJavaScriptversionspriortoECMAScript6(ES6)don'tincludenativemodulesupport.Developerswereforcedtodeveloptheirownmoduleloaders.Theopensourcecommunitytriedtocomeupwithimprovedsolutionsovertheyears.Asaresult,todaythereareseveralmoduleloadersavailable,andeachoneusesadifferentmoduledefinitionsyntax.Themostpopularonesareasfollows:

RequireJS:RequireJSusesasyntaxknownasasynchronousmoduledefinition(AMD)Browserify:BrowserifyusesasyntaxknownasCommonJS.SystemJS:SystemJSisauniversalmoduleloader,whichmeansthatitsupportsalltheavailablemodulesyntaxes(ES6,CommonJS,AMD,andUMD).

Note

Node.jsapplicationsalsousetheCommonJSsyntax.

Fortunately,TypeScriptallowsustochoosewhichkindofmoduledefinitionsyntax(ES6,CommonJS,AMD,SystemJS,orUMD)wewanttouseatruntime.

Wecanindicateourpreferencebyusingthe--moduleflagwhencompiling:

tsc--modulecommonjsmain.ts//useCommonJS

tsc--moduleamdmain.ts//useAMD

tsc--moduleumdmain.ts//useUMD

tsc--modulesystemmain.ts//useSytemJS

Whilewecanselectfourdifferentmoduledefinitionsyntaxesatruntime.However,onlytwoareavailableatdesigntime:

Page 181: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Externalmodulesyntax(ThedefaultmodulesyntaxintheTypeScriptversionspriorto1.5)ES6modulesyntax(TherecommendedexternalmodulesyntaxinTypeScript1.5orhigher)

Itisimportanttounderstandthatwecanuseonekindofmoduledefinitionsyntaxatdesigntime(ES6,CommonJS,AMD,SystemJS,orUMD)andanotheratruntime(externalmodulesorES6).

SincethereleaseofTypeScript1.5,itisrecommendedyouusetheECMAScript6moduledefinitionsyntaxbecauseitisbasedonstandards,andinthefuture,wewillbeabletousethissyntaxatbothdesigntimeandruntime.

Wewillnowtakealookateachoftheavailablemoduledefinitionsyntaxes.

Page 182: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ES6modules–runtimeanddesigntimeTypeScript1.5introducessupportfortheES6modulesyntax.Let'sdefineanexternalmoduleusingit:

classUserModel{

//...

}

export{UserModel};

Wehavedefinedanexternalmodule.Wedon'tneedtousethenamespacekeyword,butwemustcontinuetousetheexportkeyword.Weusedtheexportkeywordatthebottomofthemodule,butitisalsopossibletouseitjustbeforetheclasskeywordlikewedidintheinternalmoduleexample:

exportclassUserModel{

//...

}

Wecanalsoexportanentityusinganalias:

classUserModel{

//...

}

export{UserModelasUser};//UserModelexportedasUser

Anexportdeclarationexportsallmeaningsofaname:

interfaceUserModel{

//...

}

classUserModel{

//...

}

export{UserModel};//Exportsbothinterfaceandfunction

Toimportamodule,wemustusetheimportkeywordasfollows:

import{UserModel}from"./models";

Theimportkeywordcreatesavariableforeachimportedcomponent.Intheprecedingcodesnippet,anewvariablenamedUserModelisdeclaredanditsvaluecontainsareferencetotheUserModelclass,whichwasdeclaredandexportedinthemodels.tsfile.

Wecanusetheexportkeywordtoimportmultipleentitiesfromonemodule:

classUserValidator{

//...

}

classTalkValidator{

Page 183: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

//...

}

export{UserValidator,TalkValidator};

Furthermore,wecanusetheimportkeywordtoimportmultipleentitiesfromasinglemoduleasfollows:

import{UserValidator,TalkValidator}from"./validation.ts"

Note

Throughouttherestofthisbook,wewillusetheES6syntaxatdesign-timeandtheCommonJSsyntaxatruntime.

Page 184: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Externalmodules–designtimeonlyBeforeTypeScript1.5,modulesweredeclaredusingakindofmodulesyntaxknownasexternalmodulesyntax.Thiskindofsyntaxwasusedatdesigntime(TypeScriptcode).However,oncecompiledintoJavaScript,itwastransformedandexecuted(runtime)intoAMD,CommonJS,UMD,orSystemJSmodules.

WeshouldtrytoavoidusingthissyntaxandusethenewES6syntaxinstead.However,wewilltakeaquicklookattheexternalmodulesyntaxbecausewemayhavetoworkonoldapplicationsoroutdateddocumentation.

Wecanimportamoduleusingtheimportkeyword:

importUser=require("./user_class");

TheprecedingcodesnippetdeclaresanewvariablenamedUser.TheUservariabletakestheexportedcontentoftheuser_classmoduleasitsvalue.

Toexportamodule,weneedtousetheexportkeyword.Wecanapplytheexportkeyworddirectlytoaclassorinterface:

exportclassUser{

//…

}

Wecanalsousetheexportkeywordonitsownbyassigningtoitthevaluethatwedesiretoexport:

classUser{

//…

}

export=User;

Externalmodulescanbecompiledintoanyoftheavailablemoduledefinitionsyntaxes(AMD,CommonJS,SystemJS,orUMD).

Page 185: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AMDmodules–runtimeonlyIfwecompiletheinitialexternalmoduleintoanAMDmodule(usingtheflag--compileamd),wewillgeneratethefollowingAMDmodule:

define(["require","exports"],function(require,exports){

varUserModel=(function(){

functionUserModel(){

}

returnUserModel;

})();

returnUserModel;

});

Thedefinefunctiontakesanarrayasitsfirstargument.Thisarraycontainsalistofthenamesofthemoduledependencies.Thesecondargumentisacallbackthatwillbeinvokedonceallthemoduledependencieshavebeenloaded.ThecallbacktakeseachofthemoduledependenciesasitsparametersandcontainsallthelogicfromourTypeScriptcomponent.Noticehowthereturntypeofthecallbackmatchesthecomponentsthatwedeclaredaspublicbyusingtheexportkeyword.AMDmodulescanthenbeloadedusingtheRequireJSmoduleloader.

Note

WewillnotdiscussAMDandRequireJSfurtherinthisbook,butifyouwanttolearnmoreaboutthem,youcandosobyvisitinghttp://requirejs.org/docs/start.html.

Page 186: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CommonJSmodules–runtimeonlyWebeginbycompilingourexternalmoduleintoaCommonJSmodule(usingtheflag--compilecommonjs).Wewillcompilethefollowingcodesnippet:

classUser{

//…

}

export=User;

Asaresult,thefollowingCommonJSmoduleisgenerated:

varUserModel=(function(){

functionUserModel(){

//…

}

returnUserModel;

})();

module.exports=UserModel;

Aswecanseeintheprecedingcodesnippet,theCommonJSmoduledefinitionsyntaxisalmostidenticaltothedeprecatedTypeScript(1.4orprior)externalmodulesyntax.

TheprecedingCommonJSmodulecanbeloadedbyaNode.jsapplicationwithoutanyadditionalchangesusingtheimportkeywordandtherequirefunction:

importUserModel=require('./UserModel');

varuser=newUserModel();

However,ifweattempttousetherequirefunctioninawebbrowser,anexceptionwillbethrownbecausetherequirefunctionisundefined.WecaneasilysolvethisproblembyusingBrowserify.

Allthatweneedtofollowisthreesimplesteps:

1. InstallBrowserifyusingnpm:

npminstall-gbrowserify

2. UseBrowserifytobundleallyourCommonJSmodulesintoaJavaScriptfilethatyoucanimportusinganHTML<script>tag.Wecandothisbyexecutingthefollowingcommand:

browserifymain.js-obundle.js

Intheprecedingcommand,main.jsisthefilethatcontainstherootmodulewithinourapplication'sdependencytree.Thebundle.jsfileistheoutputfilethatwewillbeabletoimportusingaHTMLscripttag.

3. Importthebundle.jsfileusingaHTML<script>tag.

Note

Page 187: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IfyouneedmoreinformationaboutBrowserify,visittheofficialdocumentationathttps://github.com/substack/node-browserify#usage.

Page 188: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UMDmodules–runtimeonlyIfwewanttoreleaseaJavaScriptlibraryorframework,wewillneedtocompileourTypeScriptapplicationintobothCommonJSandAMDmodules.OurlibraryshouldalsoallowdeveloperstoloaditdirectlyinawebbrowserusingaHTMLscripttag.

Thewebdevelopmentcommunityhasdevelopedthefollowingcodesnippettohelpustoachieveuniversalmoduledefinition(UMD)support:

(function(root,factory){

if(typeofexports==='object'){

//CommonJS

module.exports=factory(require('b'));

}elseif(typeofdefine==='function'&&define.amd){

//AMD

define(['b'],function(b){

return(root.returnExportsGlobal=factory(b));

});

}else{

//GlobalVariables

root.returnExportsGlobal=factory(root.b);

}

}(this,function(b){

//Youractualmodule

return{};

}));

Thiscodesnippetisgreat,butwewanttoavoidmanuallyaddingittoeverysinglemoduleinourapplication.Fortunately,thereareafewoptionsavailabletoachieveUMDsupportwithease.

Thefirstoptionistousetheflag--compileumdtogenerateoneUMDmoduleforeachmoduleinourapplication.ThesecondoptionistocreateonesingleUMDmodulethatwillcontainallthemodulesintheapplicationusingamoduleloaderknownasBrowserify.

Note

RefertotheofficialBrowserifyprojectwebsiteathttp://browserify.org/tolearnmoreaboutBrowserify.RefertotheBrowserify-standaloneoptiontolearnmoreaboutthegenerationofoneuniqueoptimizedfile.

Page 189: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SystemJSmodules–runtimeonlyWhileUMDgivesyouawaytooutputasinglemodulethatworksinbothAMDandCommonJS,SystemJSwillallowyoutouseES6modulesclosertotheirnativesemanticswithoutrequiringanES6-compatiblebrowserengine.

SytemJSisusedbyAngular2.0,whichistheupcomingversionofapopularwebapplicationdevelopmentframework.

Note

RefertotheofficialSytemJSprojectwebsiteathttps://github.com/systemjs/systemjstolearnmoreaboutSystemJS.

Thereisafreelistofcommonmodulemistakesavailableonlineathttp://www.typescriptlang.org/Handbook#modules-pitfalls-of-modules.

Page 190: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CirculardependenciesAcirculardependencyisanissuethatwecanencounterwhenworkingwithmultiplecomponentsanddependencies.Sometimes,itispossibletoreachapointinwhichonecomponent(A)hasadependencyonasecondcomponent(B),whichdependsonthefirstcomponent(A).Inthefollowinggraph,eachnodeisacomponent,andwecanobservethatthenodescircular1.tsandcircular2.tshaveacirculardependency.ThenodenameddoesNotDependOnAnything.tsdoesn'thavedependenciesandthenodenamedonlyDependsOnOtherStuff.tshasadependencyoncircular1.tsbutdoesn'thavecirculardependencies.

Thecirculardependenciesdon'tneedtonecessarilyinvolvejusttwocomponents.Wecanencounterscenariosinwhichacomponentdependsonanothercomponent,whichdependsonothercomponents,andsomeofthecomponentsinthedependencytreeenduppointingtooneoftheirparentcomponentsinthetree.

Identifyingacirculardependencyisverytimeconsuming.Fortunately,Atomincludesacommand-linetoolthatwillgenerateadependencytreegraphforusliketheprecedingone.InordertoaccesstheAtomcommandline,weneedtonavigatetoView(inthetopmenu)andthentoToggleCommandPalette.

Page 191: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AfteropeningtheToggleCommandPalette,weneedtotypeTypeScript:DependencyViewtodisplaythegraph:

Note

Ifyouwanttolearnmoreaboutdependencygraphs,youcanvisititsofficialdocumentationathttps://github.com/TypeStrong/atom-typescript/blob/master/docs/dependency-view.md.

Page 192: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wesawhowtoworkwithclasses,interfaces,andmodulesindepth.Wewereabletoreducethecomplexityofourapplicationbyusingtechniquessuchasencapsulationandinheritance.

WewerealsoabletocreateexternalmodulesandmanageourapplicationdependenciesusingtoolssuchasRequireJSorBrowserify.

Inthenextchapter,wewilldiscusstheTypeScriptruntime.

Page 193: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter5.RuntimeAftercompletingthisbook,youwillprobablybeeagertostartanewprojecttoputintopracticeallyournewknowledge.Asthenewprojectgrowsandyoudevelopmorecomplexfeatures,youmightencountersomeruntimeissues.

Weshouldbeabletoresolvedesign-timeissueswitheasebecauseinthepreviouschapter,welookedatthemainTypeScriptfeatures.

However,wehavenotlearnedmuchabouttheTypeScriptruntime.Thegoodnewsisthat,dependingonyourbackground,youmayalreadyknowalotaboutit,astheTypeScriptruntimeistheJavaScriptruntime.TypeScriptisonlyusedatdesigntime;theTypeScriptcodeisthencompiledintoJavaScriptandfinallyexecuted.TheJavaScriptruntimeisinchargeoftheexecution.IsimportanttounderstandthatweneverexecuteTypeScriptcodeandwealwaysexecuteJavaScriptcode.Forthisreason,whenwerefertotheTypeScriptruntime,wewill,infact,betalkingabouttheJavaScriptruntime.

WhenwecompileourTypeScriptcode,wewillgenerateJavaScriptcode,whichwillbeexecutedontheserverside(withNode.js)orontheclientside(inawebbrowser).Itisthenthatwemayencountersomechallengingruntimeissues.

Inthischapter,wewillcoverthefollowingtopics:

TheenvironmentTheeventloopThethisoperatorPrototypesClosures

Let'sstartbylearningabouttheenvironment.

Page 194: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheenvironmentTheruntimeenvironmentisoneofthefirstthingsthatwemustconsiderbeforewecanstartdevelopingaTypeScriptapplication.OncewehavecompiledourTypeScriptcode,itcanbeexecutedinmanydifferentJavaScriptengines.Whilethemajorityofthoseengineswillbewebbrowsers,suchasChrome,InternetExplorer,orFirefox,wemightalsowanttobeabletorunourcodeontheserversideorinadesktopapplicationinenvironmentssuchasNode.jsorRingoJS.

Itisimportanttokeepinmindthattherearesomevariablesandobjectsavailableatruntimethatareenvironment-specific.Forexample,wecouldcreatealibraryandaccessthedocument.layersvariable.WhiledocumentispartoftheW3CDocumentObjectModel(DOM)standard,thelayerspropertyisonlyavailableinInternetExplorerandisnotpartoftheW3CDOMstandard.

TheW3CdefinestheDOMasfollows:

TheDocumentObjectModelisaplatform-andlanguage-neutralinterfacethatwillallowprogramsandscriptstodynamicallyaccessandupdatethecontent,structureandstyleofdocuments.Thedocumentcanbefurtherprocessedandtheresultsofthatprocessingcanbeincorporatedbackintothepresentedpage.

Inasimilarmanner,wecanalsoaccessasetofobjectsknownastheBrowserObjectModel(BOM)fromawebbrowserruntimeenvironment.TheBOMconsistsoftheobjectsnavigator,history,screen,location,anddocument,whicharepropertiesofthewindowobject.

YouneedtorealizethattheDOMispartofthewebbrowsersbutnotpartofJavaScript.Ifwewanttorunourapplicationinawebbrowser,wewillbeabletoaccesstheDOMandBOM.However,inenvironmentslikeNode.jsorRingoJS,theywillnotbeavailable,sincetheyarestandaloneJavaScriptenvironmentscompletelyindependentofawebbrowser.Wecanalsofindotherobjectsontheserver-sideenvironments(suchasprocess.stdininNode.js)thatwillnotbeavailableifweattempttoexecuteourcodeinawebbrowser.

Asifthiswasn'tenoughwork,wealsoneedtokeepinmindtheexistenceofmultipleversionsoftheseJavaScriptenvironments.WewillhavetosupportmultiplebrowsersandmultipleversionsofNode.js.Therecommendedpracticewhendealingwiththisproblemistoaddlogicthatlooksfortheavailabilityoffeaturesratherthantheavailabilityofaparticularenvironmentorversion.

Note

Areallygoodlibraryisavailablethatcanhelpustoimplementfeaturedetectionwhendevelopingforwebbrowsers.ThelibraryiscalledModernizrandcanbedownloadedathttp://modernizr.com/.

Page 195: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheruntimeTheTypeScriptruntime(JavaScript)hasaconcurrencymodelbasedonaneventloop.ThismodelisquitedifferenttothemodelsinotherlanguagessuchasCorJava.Beforewefocusontheeventloopitself,youmustunderstandsomeruntimeconcepts.

Whatfollowsisavisualrepresentationofsomeimportantruntimeconcepts:heap,stack,queue,andframe:

Wewillnowlookattheroleofeachoftheseruntimeconcepts.

Page 196: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FramesAframeisasequentialunitofwork.Intheprecedingdiagram,theframesarerepresentedbytheblocksinsidethestack.

WhenafunctioniscalledinJavaScript,theruntimecreatesaframeinthestack.Theframeholdsthatparticularfunction'sargumentsandlocalvariables.Whenthefunctionreturns,theframeispoppedoutofthestack.Let'stakealookatanexample:

functionfoo(b){

vara=12;

returna+b+35;

}

functionbar(x){

varm=4;

returnfoo(m*x);

}

Afterdeclaringthefooandbarfunctions,weinvokethebarfunction:

bar(21);

Whenbarisexecuted,theruntimewillcreateanewframecontainingtheargumentsofbarandallthelocalvariables.Theframe(representedasasquareintheprecedingdiagram)isthenaddedtothetopofthestack.

Internally,barinvokesfoo.Whenfooisinvoked,anewframeiscreatedandallocatedinthetopofthestack.Whentheexecutionoffooisfinished(foohasreturned),thetopframeisremovedfromthestack.Whentheexecutionofbarisalsocomplete,itisremovedfromthestackaswell.

Now,let'simaginewhatwouldhappenifthefoofunctioninvokedthebarfunction.Wewouldcreateanever-endingfunctioncallloop.Witheachfunctioncall,anewframewouldbeaddedtothestack,andeventually,therewouldbenomorespaceinthestack,andanerrorwouldbethrown.Mostdevelopersarefamiliarwiththiserror,knownasastackoverflowerror.

Page 197: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StackThestackcontainsthesequentialsteps(frames)thatamessageneedstoexecute.AstackisadatastructurethatrepresentsasimpleLastInFirstOut(LIFO)collectionofobjects.Therefore,whenaframeisaddedtothestack,itisalwaysaddedtothetopofthestack.

SincethestackisaLIFOcollection,theeventloopprocessestheframesstoredinitfromtoptobottom.Thedependenciesofaframeareaddedtothetopofitinthestacktoensurethatallthedependenciesofeachoftheframesaremet.

Page 198: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

QueueThequeuecontainsalistofmessageswaitingtobeprocessed.Eachmessageisassociatedwithafunction.Whenthestackisempty,amessageistakenoutofthequeueandprocessed.Theprocessingconsistsofcallingtheassociatedfunctionandaddingtheframestothestack.Themessageprocessingendswhenthestackbecomesemptyagain.

Inthepreviousruntimediagram,theblocksinsidethequeuerepresentthemessages.

Page 199: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

HeapTheheapisamemorycontainerthatisnotawareoftheorderoftheitemsstoredinit.Theheapcontainsallthevariablesandobjectscurrentlyinuse.Itmayalsocontainframesthatarecurrentlyoutofscopebuthavenotyetbeenremovedfrommemorybythegarbagecollector.

Page 200: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheeventloopConcurrencyistheabilityfortwoormoreoperationstobeexecutedsimultaneously.Theruntimeexecutiontakesplaceononesinglethread,whichmeansthatwecannotachieverealconcurrency.

Theeventloopfollowsarun-to-completionapproach,whichmeansthatitwillprocessamessagefrombeginningtoendbeforeanyothermessageisprocessed.

Note

AswediscussedinChapter3,WorkingwithFunctions,wecanusetheyieldkeywordandgeneratorstopausetheexecutionofafunction.

Everytimeafunctionisinvoked,anewmessageisaddedtothequeue.Ifthestackisempty,thefunctionisprocessed(theframesareaddedtothestack).

Whenalltheframeshavebeenaddedtothestack,thestackisclearedfromtoptobottom.Attheendoftheprocess,thestackisemptyandthenextmessageisprocessed.

Note

Webworkerscanperformancebackgroundtasksinadifferentthread.Theyhavetheirownqueue,heap,andstack.

Oneoftheadvantagesoftheeventloopisthattheexecutionorderisquitepredictableandeasytofollow.Anotherimportantadvantageoftheeventloopapproachisthatitfeaturesnon-blockingI/O.Thismeansthatwhentheapplicationiswaitingforaninputandoutput(I/O)operationtofinish,itcanstillprocessotherthings,suchasuserinput.

Adisadvantageofthisapproachisthatifamessagetakestoolongtocomplete,theapplicationbecomesunresponsive.Goodpracticeistomakemessageprocessingshort,andifpossible,splitonemessagefunctionintoseveralmessagesfunctions.

Page 201: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThethisoperatorInJavaScript,thethisoperatorbehavesalittledifferentlythanotherlanguages.Thevalueofthethisoperatorisoftendeterminedbythewayafunctionisinvoked.Itsvaluecannotbesetbyassignmentduringexecution,anditmaybedifferenteachtimeafunctionisinvoked.

Note

Thethisoperatoralsohassomedifferenceswhenusingthestrictandnonstrictmodes.Tolearnmoreaboutthestrictmode,refertohttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode.

Page 202: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThethisoperatorintheglobalcontextIntheglobalcontext,thethisoperatorwillalwayspointtotheglobalobject.Inawebbrowser,thewindowobjectistheglobalobject:

console.log(this===window);//true

this.a=37;

console.log(window.a);//37

console.log(this.document===document===window.document);//true

Page 203: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThethisoperatorinafunctioncontextThevalueofthisinsideafunctiondependsonhowthefunctionisinvoked.Ifwesimplyinvokeafunctioninthenonstrictmode,thevalueofthiswithinthefunctionwillpointtotheglobalobject:

functionf1(){

returnthis;

}

f1()===window;//true

However,ifweinvokeafunctioninthestrictmode,thevalueofthiswithinthefunction'sbodywillpointtoundefined:

console.log(this);//global(window)

functionf2(){

"usestrict";

returnthis;//undefined

}

console.log(f2());//undefined

console.log(this);//window

However,thevalueofthethisoperatorinsideafunctioninvokedasaninstancemethodpointstotheinstance.Inotherwords,thevalueofthethisoperatorwithinafunctionthatispartofaclasspointstothatclass:

varp={

age:37,

getAge:function(){

returnthis.age;//thispointstotheclassinstance(p)

}

};

console.log(p.getAge());//37

Intheprecedingexample,wehaveusedobjectliteralnotationtodefineanobjectnamedp,butthesameapplieswhendeclaringobjectsusingprototypes:

functionPerson(){}

Person.prototype.age=37;

Person.prototype.getAge=function(){

returnthis.age;

}

varp=newPerson();

p.age;//37

p.getAge();//37

Whenafunctionisusedasaconstructor(withthenewkeyword),thethisoperatorpointstotheobjectbeingconstructed:

functionPerson(){//functionusedasaconstructor

this.age=37;

Page 204: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

varp=newPerson();

console.log(p.age);//logs37

Page 205: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thecall,apply,andbindmethodsAllthefunctionsinheritthecall,apply,andbindmethodsfromFunction.prototype.Wecanusethesemethodstosetthevalueofthethisoperatorwhenitisusedinsidethebodyofafunction.

Thecallandapplymethodsarealmostidentical;bothmethodsallowustoinvokeafunctionandsetthevalueofthethisoperatorwithinthefunction.Themaindifferencebetweencallandapplyisthatwhileapplyletsusinvokethefunctionwithargumentsasanarrayandcallrequiresthefunctionparameterstobelistedexplicitly.

Tip

AusefulmnemonicisA(apply)forarrayandC(call)forcomma.

Let'stakealookatanexample.WewillstartbydeclaringaclassnamedPerson.Thisclasshastwoproperties(nameandsurname)andonemethod(greet).Thegreetmethodusesthethisoperatortoaccessthenameandsurnameinstanceproperties:

classPerson{

publicname:string;

publicsurname:string;

constructor(name:string,surname:string){

this.name=name;

this.surname=surname;

}

publicgreet(city:string,country:string){

//weusethethisoperatortoaccessnameandsurname

varmsg=`Hi,mynameis${this.name}${this.surname}.`;

msg+=`I'mfrom${city}(${country}).`;

console.log(msg);

}

}

AfterdeclaringthePersonclass,wewillcreateaninstance:

varperson=newPerson("remo","jansen");

Ifweinvokethegreetmethod,itwillworkasexpected:

person.greet.("Seville","Spain");

//Hi,mynameisremojansen.I'mfromSeville(Spain).

Alternatively,wecaninvokethemethodusingthecallandapplyfunctions.Wehavesuppliedthepersonobjectasthefirstparameterofbothfunctionsbecausewewantthethisoperator(insidethegreetmethod)totakepersonasitsvalue:

person.greet.call(person,"seville","spain");

person.greet.apply(person,["seville","spain"]);

Page 206: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Ifweprovideadifferentvaluetobeusedasthevalueofthis,wewillnotbeabletoaccessthenameandsurnamepropertieswithinthegreetfunction:

person.greet.call(null,"seville","spain");

person.greet.apply(null,["seville","spain"]);

//Hi,mynameisundefined.I'mfromsevillespain.

Thetwoprecedingexamplesmayseemuselessbecausethefirstoneinvokedthefunctiondirectlyandthesecondonecausedanunexpectedbehavior.Theapplyandcallmethodsmakesenseonlywhenwewantthethisoperatortotakeadifferentvaluewhenafunctionisinvoked:

varvalueOfThis={name:"anakin",surname:"skywalker"};

person.greet.call(valueOfThis,"mosespa","tatooine");

person.greet.apply(valueOfThis,["mosespa","tatooine"]);

//Hi,mynameisanakinskywalker.I'mfrommosespatatooine.

Thebindmethodcanbeusedtosetthevalueofthethisoperator(withinafunction)regardlessofhowitisinvoked.

Whenweinvokeafunction'sbindmethod,itreturnsanewfunctionwiththesamebodyandscopeastheoriginalfunction,butthethisoperator(withinthebodyfunction)ispermanentlyboundtothefirstargumentofbind,regardlessofhowthefunctionisinvoked.

Let'stakealookatanexample.WewillstartbycreatinganinstanceofthePersonclassthatwedeclaredinthepreviousexample:

varperson=newPerson("remo","jansen");

Then,wecanusebindtosetthegreetfunctiontobeanewfunctionwiththesamescopeandbody:

vargreet=person.greet.bind(person);

Ifwetrytoinvokethegreetfunctionusingbindandapply,justlikewedidinthepreviousexample,wewillbeabletoobservethatthistimethethisoperatorwillalwayspointtotheobjectinstanceindependentofhowthefunctionisinvoked:

greet.call(person,"seville","spain");

greet.apply(person,["seville","spain"]);

//Hi,mynameisremojansen.I'mfromsevillespain.

greet.call(null,"seville","spain");

greet.apply(null,["seville","spain"]);

//Hi,mynameisremojansen.I'mfromsevillespain.

varvalueOfThis={name:"anakin",surname:"skywalker"};

greet.call(valueOfThis,"mosespa","tatooine");

greet.apply(valueOfThis,["mosespa","tatooine"]);

//Hi,mynameisremojansen.I'mfrommosespatatooine.

Page 207: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

Usingtheapply,call,andbindfunctionsisnotrecommendedunlessyoureallyknowwhatyouaredoing,becausetheycanleadtocomplexruntimeissuesforotherdevelopers.

Oncewebindanobjecttoafunctionwithbind,wecannotoverrideit:

varvalueOfThis={name:"anakin",surname:"skywalker"};

vargreet=person.greet.bind(valueOfThis);

greet.call(valueOfThis,"mosespa","tatooine");

greet.apply(valueOfThis,["mosespa","tatooine"]);

//Hi,mynameisremojansen.I'mfrommosespatatooine.

Note

Theuseofthebind,apply,andcallmethodsisoftendiscouragedbecauseitcanleadtoconfusion.Modifyingthedefaultbehaviorofthethisoperatorcanleadtoreallyunexpectedresults.Remembertousethesemethodsonlywhenstrictlynecessaryandtodocumentyourcodeproperlytoreducetheriskcausedbypotentialmaintainabilityissues.

Page 208: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrototypesWhenwecompileaTypeScriptprogram,allclassesandobjectsbecomeJavaScriptobjects.Sometimes,wewillencounterourapplicationbehavingunexpectedlyatruntime,andwewillnotbeabletoidentifyandunderstandtherootcauseofthisbehaviorwithoutagoodunderstandingofhowinheritanceworksinJavaScript.Thisunderstandingwillallowustohavemuchbettercontroloverourapplicationatruntime.

Theruntimeinheritancesystemusesaprototypalinheritancemodel.Inaprototypalinheritancemodel,objectsinheritfromobjects,andtherearenoclassesavailable.However,wecanuseprototypestosimulateclasses.Let'sseehowitworks.

Atruntime,almosteveryJavaScriptobjecthasaninternalpropertycalledprototype.Thevalueoftheprototypeattributeisanobject,whichcontainssomeattributes(data)andmethods(behavior).

InTypeScript,wecanuseaclass-basedinheritancesystem:

classPerson{

publicname:string;

publicsurname:string;

publicage:number=0;

constructor(name:string,surname:string){

this.name=name;

this.surname=surname;

}

greet(){

varmsg=`Hi!mynameis${this.name}${this.surname}`;

msg+=`I'm${this.age}`;

}

}

WehavedefinedaclassnamedPerson.Atruntime,thisclassisdeclaredusingprototypesinsteadofclasses:

varPerson=(function(){

functionPerson(name,surname){

this.age=0;

this.name=name;

this.surname=surname;

}

Person.prototype.greet=function(){

varmsg="Hi!mynameis"+this.name+

""+this.surname;

msg+="I'm"+this.age;

};

returnPerson;

})();

TheTypeScriptcompilerwrapstheobjectdefinition(wewillnotreferitastheclass

Page 209: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

definitionbecausetechnically,itisnotaclass)withanimmediatelyinvokedfunctionexpression(IIFE).InsidetheIIFE,wecanfindafunctionnamedPerson.IfweexaminethefunctionandcompareittotheTypeScriptclass,wewillnoticethatittakesthesameparameters,liketheconstructorintheTypeScriptclass.ThisfunctionisusedtocreatenewinstancesofthePersonclass.

Aftertheconstructor,wecanseethedefinitionofthegreetmethod.Asyoucansee,theprototypeattributeisusedtoattachthegreetmethodtothePersonclass.

Page 210: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InstancepropertiesversusclasspropertiesAsJavaScriptisadynamicprogramminglanguage,wecanaddpropertiesandmethodstoaninstanceofanobjectatruntime;andtheydon'tneedtobepartoftheobject(class)itself.Let'stakealookatanexample:

functionPerson(name,surname){

//instanceproperties

this.name=name;

this.surname=surname;

}

varme=newPerson("remo","jansen");

me.email="[email protected]";

Here,wedefinedaconstructorfunctionforanobjectnamedPerson,whichtakestwovariables(nameandsurname)asarguments.Then,wehavecreatedaninstanceofthePersonobjectandaddedanewpropertynamedemailtoit.Wecanuseafor…instatementtocheckthepropertiesofmeatruntime:

for(varpropertyinme){

console.log("property:"+property+",value:'"+me[property]+"'");

}

//property:name,value:'remo'

//property:surname,value:'jansen'

//property:email,value:'[email protected]'

//property:greet,value:'function(city,country){

//varmsg="Hi,mynameis"+this.name+""+//this.surname;

//msg+="\nI'mfrom"+city+""+country;

//console.log(msg);

//}'

Allthesepropertiesareinstancepropertiesbecausetheyholdavalueforeachnewinstance.If,forexample,wecreateanewinstanceofPerson,bothinstanceswillholdtheirownvalues:

varhero=newPerson("John","117");

hero.name;//"John"

me.name;//"remo"

Wehavedefinedtheseinstancepropertiesusingthethisoperator,becauseintheclassconstructor,thethisoperatorpointstotheobject'sprototype.Thisexplainswhywecanalternativelydefineinstancepropertiesthroughtheobject'sprototype:

Person.prototype.name=name;//instanceproperty

Person.prototype.name=surname;//instanceproperty

Wecanalsodeclareclasspropertiesandmethods.Themaindifferenceisthatthevalueofclasspropertiesandmethodsissharedbetweenalltheinstancesofanobject.Classpropertiesandmethodsaresometimescalledstaticpropertiesandmethods.

Classpropertiesareoftenusedtostorestaticvalues:

Page 211: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

functionMathHelper(){

/*...*/

}

//classproperty

MathHelper.PI=3.14159265359;

Classmethodsarealsooftenusedasutilityfunctionsthatperformcalculationsuponsuppliedparametersandreturnaresult:

functionMathHelper(){/*...*/}

//classmethod

MathHelper.areaOfCircle=function(radius){

returnradius*radius*this.PI;

}

//classproperty

MathHelper.PI=3.14159265359;

Intheprecedingexample,wehaveaccessedaclassattribute(PI)fromaclassmethod(areaOfCircle).Wecanaccessclasspropertiesfrominstancemethods,butwecannotaccessinstancepropertiesormethodsfromclasspropertiesormethods.WecandemonstratethisbydeclaringPIasaninstancepropertyinsteadofaclassproperty:

functionMathHelper(){

//instanceproperty

this.PI=3.14159265359;

}

IfwethenattempttoaccessPIfromaclassmethod,itwillbeundefined:

//classmethod

MathHelper.areaOfCircle=function(radius){

returnradius*radius*this.PI;//this.PIisundefined

}

MathHelper.areaOfCircle(5);//NaN

Wearenotsupposedtoaccessclassmethodsorpropertiesfrominstancemethods,butthereisawaytodoit.Wecanachieveitusingtheprototype'sconstructorproperty.Wecanalsodemonstratethisasfollows:

functionMathHelper(){/*...*/}

//classproperty

MathHelper.PI=3.14159265359;

//instancemethod

MathHelper.prototype.areaOfCircle=function(radius){

returnradius*radius*this.constructor.PI;

}

varmath=newMathHelper();

Page 212: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

console.log(MathHelper.areaOfCircle(5));//78.53981633975

WecanaccessPI(theclassproperty)fromareaOfCircle(theinstancemethod)usingtheprototype'sconstructorpropertybecausethispropertyreturnsareferencetotheobject'sconstructor.

InsideareaOfCircle,thethisoperatorreturnsareferencetotheobject'sprototype:

this===MathHelper.prototype//true

Wemaydeducethatthis.constructorisequaltoMathHelper.prototype.constructorand,therefore,MathHelper.prototype.constructorisequaltoMathHelper.

Page 213: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrototypalinheritanceYoumightbewonderinghowtheextendskeywordworks.Let'screateanewTypeScriptclass,whichinheritsfromthePersonclass,tohelpyouunderstandit:

classSuperHeroextendsPerson{

publicsuperpower:string;

constructor(name:string,surname:string,superpower:string){

super(name,surname);

this.superpower=superpower;

}

userSuperPower(){

return`I'musingmy${this.superpower}`

}

}

TheprecedingclassisnamedSuperHeroandextendsthePersonclass.Ithasoneextraattribute(superpower)andmethod(useSuperPower).Ifwecompilethecode,wewillnoticethefollowingpieceofcode:

var__extends=this.__extends||function(d,b){

for(varpinb)if(b.hasOwnProperty(p))d[p]=b[p];

function__(){this.constructor=d;}

__.prototype=b.prototype;

d.prototype=new__();

};

ThispieceofcodeisgeneratedbyTypeScript.Eventhoughitisareallysmallpieceofcode,itshowcasesalmosteveryconceptcontainedinthischapter,andunderstandingitcanbequitechallenging.Wemightneedtoexamineitmultipletimestounderstandit,buttheeffortisworthit.Let'stakealookatthefunction.

Beforethefunctionexpressionisevaluatedforthefirsttime,thethisoperatorpointstotheglobalobject,whichdoesnotcontainamethodnamed__extends.Thismeansthatthe__extendsvariableisundefinedatthispoint:

console.log(this.__extends);//undefined

Whenthefunctionexpressionisevaluatedforthefirsttime,thevalueofthefunctionexpression(ananonymousfunction)isassignedtothe__extendspropertyintheglobalscope:

console.log(this.__extends);//extends(n,e,t);

TypeScriptgeneratesthefunctionexpressiononceforeachTypeScriptfilecontainingtheextendskeyword.However,thefunctionexpressionisonlyevaluatedonce(whenthe__extendsvariableisundefined).Thisbehaviorisimplementedinthefirstlineofcode:

var__extends=this.__extends||function(d,b){//...

Page 214: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thefirsttimethislineofcodeisexecuted,thefunctionexpressionisevaluated.Thevalueofthefunctionexpressionisananonymousfunction,whichisassignedtothe__extendsvariableintheglobalscope.Asweareintheglobalscope,var__extendsandthis._extendsrefertothesamevariableatthispoint.

Whenanewfileisexecuted,the__extendsvariableisalreadyavailableintheglobalscopeandthefunctionexpressionisnotevaluated.Thismeansthatthevalueofthefunctionexpressionisonlyassignedtothe__extendsvariableonce.

Asyoualreadyknow,thevalueofthefunctionexpressionisananonymousfunction.Let'snowfocusonit:

function(d,b){

for(varpinb)if(b.hasOwnProperty(p))d[p]=b[p];

function__(){this.constructor=d;}

__.prototype=b.prototype;

d.prototype=new__();

}

Thisfunctiontakestwoargumentsnameddandb.Whenweinvokeit,weshouldpassaderivedobjectconstructor(d)andabaseobjectconstructor(b).

Thefirstlineinsidetheanonymousfunctioniterateseachclasspropertyandmethodfromthebaseclassandcreatestheircopyinthederivedclass:

for(varpinb)if(b.hasOwnProperty(p))d[p]=b[p];

Note

Whenweuseafor…instatementtoiterateaninstanceofanobject,itwilliteratetheobject'sinstanceproperties.However,ifweuseafor…instatementtoiteratethepropertiesofanobject'sconstructor,thestatementwilliterateitsclassproperties.Intheprecedingexample,thefor…instatementisusedtoinherittheobject'sclasspropertiesandmethods.Toinherittheinstanceproperties,wewillcopytheobject'sprototype.

Thesecondlinedeclaresanewconstructorfunctionnamed__,andinsideit,thethisoperatorisusedtoaccessitsprototype:

function__(){this.constructor=d;}

Theprototypecontainsaspecialpropertynamedconstructor,whichreturnsareferencetotheobject'sconstructor.Thefunctionnamed__andthis.constructorarepointingtothesamevariableatthispoint.Thevalueofthederivedobjectconstructor(d)isthenassignedtothe__constructor.

Inthethirdline,thevalueoftheprototypeobjectfromthebaseobjectconstructorisassignedtotheprototypeofthe__objectconstructor:

__.prototype=b.prototype;

Page 215: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Inthelastline,anew__()isinvoked,andtheresultisassignedtothederivedclass(d)prototype.Byperformingallthesesteps,wehaveachievedallthatweneedtoinvokethefollowing:

varinstance=newd():

Upondoingso,wewillgetanobjectthatcontainsallthepropertiesfromboththederivedclass(d)andthebaseclass(b).Furthermore,theinstanceofoperatorwillworkaswewouldexpect:

varsuperHero=newSuperHero();

console.log(superHeroinstanceofPerson);//true

console.log(superHeroinstanceofSuperHero);//true

WecanseethefunctioninactionbyexaminingtheruntimecodethatdefinestheSuperHeroclass:

varSuperHero=(function(_super){

__extends(SuperHero,_super);

functionSuperHero(name,surname,superpower){

_super.call(this,name,surname);

this.superpower=superpower;

}

SuperHero.prototype.userSuperPower=function(){

return"I'musingmy"+superpower;

};

returnSuperHero;

})(Person);

WecanseeanIIFEhereagain.Thistime,theIIFEtakesthePersonobjectconstructorastheargument.Insidethefunction,wewillrefertothisargumentusingthename_super.InsidetheIIFE,the__extendsfunctionisinvokedandtheSuperHero(derivedclass)and_super(baseclass)argumentsarepassedtoit.

Inthenextline,wecanfindthedeclarationoftheSuperHeroobjectconstructorandtheuseSuperPowerfunction.WecanuseSuperHeroasanargumentof__extendbeforeitisdeclared,becausefunctionsdeclarationsarehoistedtothetopofthescope.

Note

Functionexpressionsarenothoisted.Whenweassignafunctiontoavariableinafunctionexpression,thevariableishoisted,butitsvalue(thefunctionitself)isnothoisted.

InsidetheSuperHeroconstructor,thebaseclass(Person)constructorisinvokedusingthecallmethod:

_super.call(this,name,surname);

Aswediscussedpreviouslyinthischapter,wecanusecalltosetthevalueofthethisoperatorinafunctioncontext.Inthiscase,wearepassingthethisoperator,whichpointsto

Page 216: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

theinstanceofSuperHerobeingcreated:

functionPerson(name,surname){

//thispointstotheinstanceofSuperHerobeingcreated

this.name=name;

this.surname=surname;

}

Page 217: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheprototypechainWhenwetrytoaccessapropertyormethodofanobject,theruntimewillsearchforthatpropertyormethodintheobject'sownpropertiesandmethods.Ifitisnotfound,theruntimewillcontinuesearchingthroughtheobject'sinheritedpropertiesbynavigatingtheentireinheritancetree.Asaderivedobjectislinkedtoitsbaseobjectthroughtheprototypeproperty,werefertothisinheritancetreeastheprototypechain.

Let'stakealookatanexample.WewilldeclaretwosimpleTypeScriptclassesnamedBaseandDerived:

classBase{

publicmethod1(){return1;};

publicmethod2(){return2;};

}

classDerivedextendsBase{

publicmethod2(){return3;};

publicmethod3(){return4;};

}

Now,wewillexaminetheJavaScriptcodegeneratedbyTypeScript:

varBase=(function(){

functionBase(){

}

Base.prototype.method1=function(){return1;};

;

Base.prototype.method2=function(){return2;};

;

returnBase;

})();

varDerived=(function(_super){

__extends(Derived,_super);

functionDerived(){

_super.apply(this,arguments);

}

Derived.prototype.method2=function(){return3;};

;

Derived.prototype.method3=function(){return4;};

;

returnDerived;

})(Base);

WecanthencreateaninstanceoftheDerivedclass:

varderived=newDerived();

Ifwetrytoaccessthemethodnamedmethod1,theruntimewillfinditintheinstance'sownproperties:

console.log(derived.method1());//1

Page 218: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theinstancealsohasitsownpropertynamedmethod2(withvalue2),butthereisalsoaninheritedpropertynamedmethod2(withvalue3).Theobject'sownproperty(method2withvalue3)preventsaccesstotheprototypeproperty(method2withvalue2).Thisisknownaspropertyshadowing:

console.log(derived.method2());//3

Theinstancedoesnothaveitsownpropertynamedmethod3,butithasapropertynamedmethod3initsprototype:

console.log(derived.method3());//4

Boththeinstanceandtheobjectsintheprototypechain(theBaseclass)don'thaveapropertynamedmethod4:

console.log(derived.method4());//error

Page 219: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AccessingtheprototypeofanobjectPrototypescanbeaccessedinthreedifferentways:

Person.prototype:WecanaccesstheprototypeofafunctiondirectlyusingtheprototypeattributePerson.getPrototypeOf(person):WewantthisfunctiontoaccesstheprototypeofaninstanceofanobjectwecanusethegetPrototypeOffunctionperson.__proto__:Thisisapropertythatexposestheinternalprototypeoftheobjectthroughwhichitisaccessed

Note

Theuseof__proto__iscontroversialandhasbeendiscouragedbymany.ItwasneveroriginallyincludedintheECMAScriptlanguagespec,butmodernbrowsersdecidedtoimplementitanyway.Today,the__proto__propertyhasbeenstandardizedintheECMAScript6languagespecificationandwillbesupportedinthefuture,butitisstillaslowoperationthatshouldbeavoidedifperformanceisaconcern.

Page 220: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThenewoperatorWecanusethenewoperatortogenerateaninstanceofPerson:

varperson=newPerson("remo","jansen");

Theruntimedoesnotfollowaclass-basedinheritancemodel.Whenweusethenewoperator,theruntimecreatesanewobjectthatinheritsfromthePersonclassprototype.

Wemayconcludethatthebehaviorofthenewoperatoratruntime(JavaScript)isnotreallydifferentfromtheextendskeywordatdesigntime(TypeScript).

Page 221: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ClosuresClosuresareoneofthemostpowerfulfeaturesavailableatruntime,buttheyarealsooneofthemostmisunderstood.TheMozilladevelopernetworkdefinesclosuresasfollows:

"Closuresarefunctionsthatrefertoindependent(free)variables.Inotherwords,thefunctiondefinedintheclosure'remembers'theenvironmentinwhichitwascreated."

Weunderstandindependent(free)variablesasvariablesthatpersistbeyondthelexicalscopefromwhichtheywerecreated.Let'stakealookatanexample:

functionmakeArmy(){

varshooters=[]

for(vari=0;i<10;i++){

varshooter=function(){//ashooterisafunction

alert(i)//whichshouldalertit'snumber

}

shooters.push(shooter)

}

returnshooters;

}

WehavedeclaredafunctionnamedmakeArmy.Insidethefunction,wehavecreatedanarrayoffunctionsnamedshooters.Eachfunctionintheshootersarraywillalertanumber,thevalueofwhichwassetfromthevariableiinsideaforstatement.WewillnowinvokethemakeArmyfunction:

vararmy=makeArmy();

Thearmyvariableshouldnowcontainthearrayoffunctionsshooters.However,wewillnoticeaproblemifweexecutethefollowingpieceofcode:

army[0]();//10(expected0)

army[5]();//10(expected5)

Theprecedingcodesnippetdoesnotworkasexpectedbecausewemadeoneofthemostcommonmistakesrelatedtoclosures.WhenwedeclaredtheshooterfunctioninsidethemakeArmyfunction,wecreatedaclosurewithoutknowingit.

Thereasonforthisisthatthefunctionsassignedtoshooterareclosures;theyconsistofthefunctiondefinitionandthecapturedenvironmentfromthemakeArmyfunction'sscope.Tenclosureshavebeencreated,buteachonesharesthesamesingleenvironment.Bythetimetheshooterfunctionsareexecuted,theloophasrunitscourseandtheivariable(sharedbyalltheclosures)hasbeenleftpointingtothelastentry(10).

Onesolutioninthiscaseistousemoreclosures:

functionmakeArmy(){

varshooters=[]

Page 222: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

for(vari=0;i<10;i++){

(function(i){

varshooter=function(){

alert(i);

}

shooters.push(shooter)

})(i);

}

returnshooters;

}

vararmy=makeArmy();

army[0]();//0

army[5]();//5

Thisworksasexpected.Ratherthantheshooterfunctionssharingasingleenvironment,theimmediatelyinvokedfunctioncreatesanewenvironmentforeachone,inwhichireferstothecorrespondingvalue.

Page 223: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StaticvariableswithclosuresIntheprevioussection,wesawthatwhenavariableisdeclaredinaclosurecontextitcanbesharedbetweenmultipleinstancesofaclass,orinotherwords,thevariablebehavesasastaticvariable.

Wewillnowseehowwecancreatevariablesandmethodsthatbehavelikestaticvariables.Let'sstartbydeclaringaTypeScriptclassnamedCounter:

classCounter{

privatestatic_COUNTER=0;

constructor(){}

private_changeBy(val){

Counter._COUNTER+=val;

}

publicincrement(){

this._changeBy(1);

}

publicdecrement(){

this._changeBy(-1);

}

publicvalue(){

returnCounter._COUNTER;

}

}

Theprecedingclasscontainsastaticmembernamed_COUNTER.TheTypeScriptcompilertransformsitintothefollowingresultingcode:

varCounter=(function(){

functionCounter(){

}

Counter.prototype._changeBy=function(val){

Counter._COUNTER+=val;

};

Counter.prototype.increment=function(){

this._changeBy(1);

};

Counter.prototype.decrement=function(){

this._changeBy(-1);

};

Counter.prototype.value=function(){

returnCounter._COUNTER;

};

Counter._COUNTER=0;

returnCounter;

})();

Asyoucanobserve,thestaticvariableisdeclaredbytheTypeScriptcompilerasaclassproperty(asopposedtoaninstanceproperty).Thecompilerusesaclasspropertybecauseclasspropertiesaresharedacrossallinstancesofaclass.

Page 224: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Alternatively,wecouldwritesomeJavaScript(rememberthatallvalidJavaScriptisvalidTypeScript)codetoemulatestaticpropertiesusingclosures:

varCounter=(function(){

//closurecontext

var_COUNTER=0;

functionchangeBy(val){

_COUNTER+=val;

}

functionCounter(){};

Counter.prototype.increment=function(){

changeBy(1);

};

Counter.prototype.decrement=function(){

changeBy(-1);

};

Counter.prototype.value=function(){

return_COUNTER;

};

returnCounter;

})();

TheprecedingcodesnippetdeclaresaclassnamedCounter.Theclasshassomemethodsusedtoincrement,decrement,andreadthevariablenamed_COUNTER.The_COUNTERvariableitselfisnotpartoftheobjectprototype.

TheCounterconstructorfunctionispartofaclosure.Asaresult,alltheinstancesoftheCounterclasswillsharethesameclosurecontext,whichmeansthatthecontext(thevariablecounterandthefunctionchangeBy)willbehaveasasingleton.

Note

Thesingletonpatternrequiresanobjecttobedeclaredasastaticvariabletoavoidtheneedtocreateitsinstancewheneveritisrequired.Theobjectinstanceis,therefore,sharedbyallthecomponentsintheapplication.Thesingletonpatternisfrequentlyusedinscenarioswhereitisnotbeneficial,whichintroducesunnecessaryrestrictionsinsituationswhereauniqueinstanceofaclassisnotactuallyrequired,andintroducesglobalstatesintoanapplication.

So,younowknowthatitispossibletouseclosurestoemulatestaticvariables:

varcounter1=newCounter();

varcounter2=newCounter();

console.log(counter1.value());//0

console.log(counter2.value());//0

counter1.increment();

counter1.increment();

console.log(counter1.value());//2

console.log(counter2.value());//2(expected0)

counter1.decrement();

Page 225: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

console.log(counter1.value());//1

console.log(counter2.value());//1(expected0)

Page 226: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrivatememberswithclosuresWehaveseenthattheclosurefunctioncanaccessvariablesthatpersistbeyondthelexicalscopefromwhichtheywerecreated.Thesevariablesarenotpartofthefunctionprototypeorbody,buttheyarepartoftheclosurefunctioncontext.

Asthereisnowaytodirectlyaccessthecontextofaclosurefunction,thecontextvariablesandmethodscanbeusedtoemulateprivatemembers.Themainadvantageofusingclosurestoemulateprivatemembers(insteadoftheTypeScriptprivateaccessmodifier)isthatclosureswillpreventaccesstoprivatemembersatruntime.

TypeScriptavoidsemulatingprivatepropertiesatruntime.TheTypeScriptcompilerwillthrowanerroratcompilationtimeifweattempttoaccessaprivatemember.

However,TypeScriptavoidstheuseofclosurestoemulateprivatememberstoimprovetheapplicationperformance.Ifweaddorremoveanaccessmodifiertoorfromoneofourclasses,theresultingJavaScriptcodewillnotchangeatall.Thismeansthatprivatemembersofaclassbecomepublicmembersatruntime.

However,itispossibletouseclosurestoemulateprivatepropertiesatruntime.Justlikewhenweemulatedastaticvariableusingclosures,wecanonlyachievethiskindofadvancedcontroloverthebehaviorofclosuresbywritingpureJavaScript.Let'stakealookatanexample:

functionmakeCounter(){

//closurecontext

var_COUNTER=0;

functionchangeBy(val){

_COUNTER+=val;

}

functionCounter(){};

Counter.prototype.increment=function(){

changeBy(1);

};

Counter.prototype.decrement=function(){

changeBy(-1);

};

Counter.prototype.value=function(){

return_COUNTER;

};

returnnewCounter();

};

Theprecedingclassisalmostidenticaltotheclassthatwepreviouslydeclaredtodemonstratehowtoemulatestaticvariablesatruntimeusingclosures.

Page 227: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thistime,anewclosurecontextiscreatedeverytimeweinvokethemakeCounterfunction,soeachnewinstanceofCounterwillrememberanindependentcontext(counterandchangeBy):

varcounter1=makeCounter();

varcounter2=makeCounter();

console.log(counter1.value());//0

console.log(counter2.value());//0

counter1.increment();

counter1.increment();

console.log(counter1.value());//2

console.log(counter2.value());//0(expected0)

counter1.decrement();

console.log(counter1.value());//1

console.log(counter2.value());//0(expected0)

Sincethecontextcannotbeaccesseddirectly,wecansaythatthevariablecounterandthechangeByfunctionareprivatemembers:

console.log(counter1.counter);//undefined

counter1.changeBy(2);//changeByisnotafunction

console.log(counter1.value());//1

Page 228: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wediscoveredhowtounderstandtheruntime,whichallowsusnotonlytoresolveruntimeissueswitheasebutalsotobeabletowritebetterTypeScriptcode.Adeepunderstandingofclosuresandprototypeswillallowyoutodevelopsomecomplexfeaturesthatitwouldhavenotbeenpossibletodevelopwithoutthisknowledge.

Inthenextchapter,wewillfocusonperformance,memorymanagement,andexceptionhandling.

Page 229: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter6.ApplicationPerformanceInthischapter,wewilltakealookathowcanwemanageavailableresourcesinanefficientmannertoachievegreatperformance.Youwillunderstandthedifferenttypesofresource,performancefactors,performanceprofilingandautomation.

Thechapterbeginsbyintroducingsomecoreperformanceconcepts,suchaslatencyorbandwidth,andcontinuesbyshowcasinghowtomeasureandmonitorperformanceaspartoftheautomatedbuildprocess.

Aswediscussedinpreviouschapters,wecanuseTypeScripttogenerateJavaScriptcodethatcanbeexecutedinmanydifferentenvironments(webbrowsers,Node.js,mobiledevices,andsoon).Inthischapter,wewillexploreperformanceoptimization,whichismainlyapplicabletothedevelopmentofwebapplications.Thefollowingtopicswillbecoveredinthischapter:

PerformanceandresourcesAspectsofperformanceMemoryprofilingNetworkProfilingCPUandGPUprofilingPerformancetestingPerformancerecommendationsPerformanceautomation

Page 230: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrerequisitesBeforewegetstarted,weneedtoinstallGoogleChromebecausewewilluseitsdevelopertoolstoperformwebperformanceanalysis.

Page 231: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformanceandresourcesBeforewegetourhandsdirtydoingsomeperformanceanalysis,monitoring,andautomation,wemustfirstspendsometimeunderstandingsomecoreconceptsandaspectsaboutperformance.

Agoodapplicationisonethathasasetofdesirablecharacteristics,whichincludesfunctionality,reliability,usability,reusability,efficiency,maintainability,andportability.Overthecourseofthisbooksofar,wehaveunderstoodalotaboutmaintainabilityandreusability.Inthischapter,wewillfocusonperformance,whichiscloselyrelatedtoreliabilityandmaintainability.

Thetermperformancereferstotheamountofusefulworkaccomplishedcomparedtothetimeandresourcesused.Aresourceisaphysical(CPU,RAM,GPU,HDD,andsoon)orvirtual(CPUtimes,RAMregions,files,andsoon)componentwithlimitedavailability.Astheavailabilityofaresourceislimited,eachresourceissharedbetweenprocesses.Whenaprocessfinishesusingaresource,itmustreleasetheresourcebeforeanyotherprocesscanuseit.Managingavailableresourcesinanefficientmannerwillhelptoreducethetimeotherprocessesspendwaitingfortheresourcestobecomeavailable.

Whenweworkonawebapplication,weneedtokeepinmindthatthefollowingresourceswillhavelimitedavailability:

CentralProcessingUnit(CPU):Thiscarriesouttheinstructionsofacomputerprogrambyperformingthebasicarithmetic,logical,control,andinput/output(I/O)operationsspecifiedbytheinstructions.GraphicsProcessorUnit(GPU):Thisisaspecializedprocessorisusedinthemanipulationandalterationofmemorytoacceleratethecreationofimagesinaframebufferintendedforoutputtoadisplay.TheGPUisusedwhenwecreateapplicationsthatusetheWebGLAPIorwhenweusesomeCSS3animations.RandomAccessMemory(RAM):Thisallowsdataitemstobereadandwritteninapproximatelythesameamountoftimeregardlessoftheorderinwhichdataitemsareaccessed.Whenwedeclareavariable,itwillbestoredinRAMmemory;whenthevariableisoutofthescope,itwillberemovedfromRAMbythegarbagecollector.HardDiskDrive(HDD)andSolidStateDrive(SSD):Bothofthesearedatastoragedevicesusedtostoreandretrieveinformation.Whendevelopingclient-sidewebapplications,wewillnothavetoworryabouttheseresourcesreallyoftenbecausetheseapplicationsdon'tusuallyextensivelyusepersistentdatastorage.However,weshouldkeepinmindthat,wheneverwestoreanobjectinapersistentmanner(cookies,localstorage,IndexedDB,andsoon),theperformanceofourapplicationwillbeaffectedbytheavailabilityoftheHDDorSSD.Networkthroughput:Thisdetermineshowmuchactualdatacanbesentperunitoftimeacrossanetwork.Thenetworkthroughputisdeterminedbyfactorssuchasthenetworklatencyorbandwidth(wewilldiscussmoreaboutthesefactorslaterinthischapter).

Page 232: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

AlltheresourcespresentedintheprecedinglistarealsolimitedwhenworkingonaNode.jsapplicationorahybridapplication.However,itisnotreallycommontoextensivelyusetheGPUwhileworkingonaNode.jsapplication,butitisapossiblescenario.

Page 233: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformancemetricsAsperformanceisinfluencedbytheavailabilityofmultipletypesofphysicalandvirtualdevice,wecanfindafewdifferentperformancemetrics(factorstomeasureperformance).Somepopularperformancemetricsincludeavailability,responsetime,processingspeed,latency,bandwidth,andscalability.Thesemeasurementmechanismsareusuallydirectlyrelatedtooneofthegeneralresources(CPU,networkthroughput,andsoon)thatwerementionedintheprevioussection.Wewillnowlookateachoftheseperformancemetricsindetail.

Page 234: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AvailabilityTheavailabilityofasystemisrelatedtoitsperformance,becauseifthesystemisnotavailableatsomestage,wewillperceiveitasbadperformance.Theavailabilitycanbeimprovedbyimprovingthereliability,maintainability,andtestabilityofthesystem.Ifthesystemiseasytotestandmaintain,itwillbeeasytoincreaseitsreliability.

Page 235: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheresponsetimeTheresponsetimeistheamountoftimethatittakestorespondtoarequestforaservice.Aserviceheredoesnotrefertoawebservice;aservicecanbeanyunitofwork.Theresponsetimecanbedividedintothreeparts:

Waittime:Thisistheamountoftimethattherequestswillspendwaitingforotherrequeststhattookplaceearliertobecompleted.Servicetime:Thisistheamountoftimethatittakesfortheservice(unitofwork)tobecompleted.Transmissiontime:Oncetheunitofworkhasbeencompleted,theresponsewillbesentbacktotherequestor.Thetimethatittakesfortheresponsetobetransmittedisknownasthetransmissiontime.

Page 236: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ProcessingspeedProcessingspeed(alsoknownasclockrate)referstothefrequencyatwhichaprocessingunit(CPUorGPU)runs.Anapplicationcontainsmanyunitsofwork.Eachunitofworkiscomposedofinstructionsfortheprocessor;usually,theprocessorscanperformaninstructionineachclocktick.Sinceafewclockticksarerequiredforanoperationtobecompleted,thehighertheclockrate(processingspeed),themoreinstructionswillbecompleted.

Page 237: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

LatencyLatencyisatermwecanapplytomanyelementsinasystem;butwhenworkingonwebapplications,wewillusethistermtorefertonetworklatency.Networklatencyindicatesanykindofdelaythatoccursindatacommunicationoverthenetwork.

Highlatencycreatesbottlenecksinthecommunicationbandwidth.Theimpactoflatencyonnetworkbandwidthcanbetemporaryorpersistent,basedontherootcauseofthedelays.Highlatencycanbecausedbyproblemsinthemedium(cablesorwirelesssignals),problemswithroutersandgateways,andanti-virus,amongotherthings.

Page 238: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BandwidthJustlikeinthecaseoflatency,wheneverwementionbandwidthinthischapter,wewillbereferringtothenetworkbandwidth.Thebandwidth,ordatatransferrate,istheamountofdatathatcanbecarriedfromonepointtoanotherinagiventime.Thenetworkbandwidthisusuallyexpressedinbitspersecond.

Note

Networkperformancecanbeaffectedbymanyfactors.Someofthesefactorscandegradethenetworkthroughput.Forexample,ahighpacketloss,latency,andjitterwillreducethenetworkthroughput,whileahighbandwidthwillincreaseit.

Page 239: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ScalabilityScalabilityistheabilityofasystemtohandleagrowingamountofwork.Asystemwithgoodscalabilitywillbeabletopasssomeperformancetests,suchasspikeorstresstesting.

Wewilldiscovermoreaboutperformancetests(suchasspikeandstress)laterinthischapter.

Page 240: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformanceanalysisPerformanceanalysis(alsoknownasperformanceprofiling)istheobservationandstudyofresourceusagebyanapplication.Wewillperformprofilinginordertoidentifyperformanceissuesinourapplications.Adifferentperformanceprofilingprocesswillbecarriedoutforeachtypeofresourceusingspecifictools.WewillnowtakealookathowwecanuseGoogleChrome'sdevelopertoolstoperformnetworkprofiling.

Page 241: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NetworkperformanceanalysisWearegoingtostartbyanalyzingnetworkperformance.Notsolongago,inordertobeabletoanalyzethenetworkperformanceofanapplication,wewouldhavehadtowriteasmallnetworkloggingapplicationourselves.Today,thingsaremucheasierthankstothearrivaloftheperformancetimingAPI(http://www.w3.org/TR/resource-timing/).TheperformancetimingAPIallowsustoaccessdetailednetworktimingdataforeachloadedresource.

ThefollowingdiagramillustratesthenetworktimingdatapointsthattheAPIprovides:

WecanaccesstheperformancetimingAPIviatheglobalobject:

window.performance

Theperformanceattributeintheglobalobjecthassomeproperties(memory,navigation,andtiming)andmethods(clearMarks,clearMeasures,andgetEntries).WecanusethegetEntriesfunctiontogetanarraythatcontainsthetamingdatapointsofeachrequest:

window.performance.getEntries()

EachentityinthearrayisaninstanceofPerformanceResourceTiming,whichcontainsthefollowinginformation:

{

connectEnd:1354.525000002468

connectStart:1354.525000002468

domainLookupEnd:1354.525000002468

domainLookupStart:1354.525000002468

Page 242: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

duration:179.89400000078604

entryType:"resource"

fetchStart:1354.525000002468

initiatorType:"link"

name:"https://developer.chrome.com/static/css/out/site.css"

redirectEnd:0

redirectStart:0

requestStart:1380.8379999827594

responseEnd:1534.419000003254

responseStart:1533.6550000065472

secureConnectionStart:0

startTime:1354.525000002468

}

Unfortunately,thetimingdatapointsintheprecedingformatmaynotbereallyuseful,buttherearetoolsthatcanhelpustoanalyzethemwithease.Thefirstofthesetoolsisabrowserextensioncalledperformance-bookmarklet.ThisextensionisopensourceandisavailableforChromeandFirefox.Theextensiondownloadlinkscanbefoundathttps://github.com/micmro/performance-bookmarklet.

Inthefollowingscreenshot,youcanseeoneofthegraphsgeneratedbytheextension.ThegraphsdisplaytheperformancetypingAPIinformationinamuchbetterway,allowingustospotperformanceissueswithease:

Alternatively,youcanusethenetworkpanelintheChromedevelopertoolstoperformnetworkperformanceprofiling.Toaccessthenetworkpanel,navigatetoView,Developer,andthenDeveloperTools:

Page 243: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

WindowsuserscanaccessthedevelopertoolsbypressingtheF12key.OSXuserscanaccessitusingtheAlt+Cmd+Ishortcut.

Oncethedevelopertoolsarevisible,youcanaccesstheNetworktabbyclickingonit:

ClickingontheNetworktabwillleadyoutoascreensimilartotheoneseenhere:

Page 244: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Asyoucanobserve,theinformationispresentedinatableinwhicheachfileloadedisdisplayedasarow.Ontheright-handside,youcanseethatoneofthecolumnsisthetimeline.ThetimelinedisplaystheperformancetimingAPIinasimilarwaytothewaythattheperformance-bookmarkletextensiondid.

Twoimportantelementsinthetimelinearetheredandbluelines.TheselinesletusknowwhentheDOMContentLoadedeventistriggered(theblueline),followingwhichtheloadeventistriggered(theredline):

Page 245: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thesetwoeventsareimportantbecausewecanexaminewhichrequestswerecompletedwhentheeventwasfiredtogetanideaofwhichcontentswereavailablefortheuserwhentheytookplace:

TheDOMContentLoadedeventisfiredwhentheenginehascompletedparsingofthemaindocumentTheloadeventisfiredwhenallthepage'sresourceshavebeenloaded

Ifyouhoveroveroneofthecellsofthetimingcolumn,youwillbeabletoseeeachoftheperformancetimingAPIdatapoints:

Page 246: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ItisinterestingtoknowthatthisdevelopertoolactuallyreadsthisinformationusingtheperformancetimingAPI.Let'sunderstandthemeaningofeachofthedatapoints:

PerformancetimingAPIdatapoint

Description

Stalled/Blocking

Thisisthetimetherequestspentwaitingbeforeitcouldbesent;thereisamaximumnumberofopenTCPconnectionsforanorigin.Whenthelimitisreached,somerequestswilldisplayblockingtimeratherthanstalledtime.

ProxyNegotiation Thisisthetimespentnegotiatingaconnectionwithaproxyserver.

DNSLookup ThisisthetimespentresolvingaDNSaddress;resolvingaDNSrequiresafullround-triptotheDNSserverforeachdomaininthepage.

InitialConnection/ Thisisthetimeittooktoestablishaconnection.

Page 247: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Connecting

SSL ThisisthetimespentestablishinganSSLconnection.

RequestSent/Sending

Thisisthetimespentissuingthenetworkrequest,typicallyafractionofamillisecond.

Waiting(TTFB)

Thisisthetimespentwaitingfortheinitialbytetobereceived—thetimetofirstbyte(TTFB).TheTTFBcanbeusedtofindoutthelatencyofaround-triptotheserverinadditiontothetimespentwaitingfortheservertodelivertheresponse.

ContentDownload/Downloading

Thisisthetimetakenfortheresponsedatatobereceived.

Page 248: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NetworkperformanceanduserexperienceNowthatyouknowhowwecananalyzenetworkperformance,itistimetoidentifytheperformancegoalsweshouldaimfor.Numerousstudieshaveprovedthatitisreallyimportanttokeeploadingtimesaslowaspossible.TheAkamaistudy,publishedinSeptember2009,interviewed1,048onlineshoppersandfoundthefollowing:

47percentofpeopleexpectawebpagetoloadintwosecondsorless40percentwillabandonawebpageifittakesmorethanthreesecondstoload52percentofonlineshoppersclaimthatquickpageloadsareimportantfortheirloyaltytoasite14percentwillstartshoppingatadifferentsiteifpageloadsareslow;23percentwillstopshoppingorevenwalkawayfromtheircomputer64percentofshopperswhoaredissatisfiedwiththeirsitevisitwillgosomewhereelsetoshopnexttime

Note

YoucanreadthefullAkamaistudyathttp://www.akamai.com/html/about/press/releases/2009/press_091409.html.

Fromtheprecedingstudyconclusions,weshouldassumethatnetworkperformancematters.Ourfirstpriorityshouldbetotrytoimprovetheloadingspeed.

Ifwetrytoimprovetheperformanceofasitetomakesurethatitloadsinlessthantwoseconds,wemightmakeacommonmistake:tryingtogettheonLoadeventtobetriggeredinundertwoseconds.

WhiletriggeringtheonLoadeventasearlyaspossiblewillprobablyimprovethenetworkperformanceofanapplication,itdoesn'tmeanthattheuserexperiencewillbeequallyimproved.TheonLoadeventisinsufficienttodetermineperformance.WecandemonstratethisbycomparingtheloadingperformanceoftheTwitterandAmazonwebsites.Asyoucanseeinthefollowingscreenshot,usershavetheopportunitytoengagewithAmazonmuchsoonerthanwithTwitter.EventhoughtheonLoadeventisthesameonbothsites,theuserexperienceisdrasticallydifferent:

Page 249: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thisexampledemonstratesthattoimprovetheuserexperience,wemusttrytoreducetheloadingtimes,butwemustalsotrytoloadthewebcontentsinsuchawaythattheuserengagementcanbeginasearlyaspossible.Toachievethis,weshouldloadallthesecondarycontentinanasynchronousmanner.

Note

RefertoChapter3,WorkingwithFunctionstolearnmoreaboutasynchronousprogrammingwithTypeScript.

Networkperformancebestpracticesandrules

Anothereasywaytoanalyzetheperformanceofawebapplicationisbyusingabest-practicestoolfornetworkperformance,suchastheGooglePageSpeedInsightsapplicationortheYahooYSlowapplication.

GooglePageSpeedInsightscanbeusedonlineorasaGoogleChromeextension.Totrythistool,youcanvisittheonlineversionathttps://developers.google.com/speed/pagespeed/insights/andinserttheURLofthewebapplicationthatyouwanttoanalyze.Injustafewseconds,youwillgetareportliketheoneinthefollowingscreenshot:

Page 250: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thereportcontainssomeeffectiverecommendationsthatwillhelpustoimprovethenetworkperformanceandoveralluserexperienceofourwebapplications.GooglePageSpeedInsightsusesthefollowingrulestoratethespeedofawebapplication:

AvoidlandingpageredirectsEnablecompressionImproveserverresponsetimeLeveragebrowsercachingMinifyresourcesOptimizeimagesOptimizeCSSDeliveryPrioritizevisiblecontentRemoverender-blockingJavaScriptUseasynchronousscripts

Whenyouusethistool,ifyouclickonthescoreofeachrules,youcanseerecommendationsanddetailsthatwillhelpyoutounderstandwhatiswrongandwhatyouneedtodotoincrease

Page 251: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thescoreachievedforoneparticularrule.

Ontheotherhand,YahooYSlowisavailableasabrowserextension,aNode.jsmodule,andaPhantomJSplugin,amongothers.Wecanfindtherightversionforourneedsathttp://yslow.org/.WhenwerunYSlow,itwillgenerateareportthatwillprovideuswithageneralscoreandadetailedscoreofthewebsite,liketheoneinthefollowingscreenshot:

YSlowusesthefollowingsetofrulestoratethespeedofawebapplication:

MinimizeHTTPrequestsUseacontentdeliverynetworkAvoidemptysrcorhrefAddanexpiresoracache-controlheaderGzipcomponentsPutstylesheetsatthetopPutscriptsatthebottomAvoidCSSexpressionsMakeJavaScriptandCSSexternalReduceDNSlookupsMinifyJavaScriptandCSSAvoidredirectsRemoveduplicatescriptsConfigureETagsMakeAJAXcacheable

Page 252: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UseGETforAJAXrequestsReducethenumberofDOMelementsPrevent404errorsReducecookiesizeUsecookie-freedomainsforcomponentsAvoidfiltersDonotscaleimagesinHTMLMakefavicon.icosmallandcacheable

Justlikebefore,whenyouusethistool,ifyouclickoneachoftherulesscoredyoucanseerecommendationsanddetailsthatwillhelpyoutounderstandwhatiswrongandwhatyouneedtodotoincreasethescoreachievedforoneparticularrule.

Note

Ifyouwanttolearnmoreaboutnetworkperformanceoptimization,pleasetakealookatthebookHighPerformanceBrowserNetworkingbyIlyaGrigorik.

Page 253: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GPUperformanceanalysisTherenderingofsomeelementsinwebapplicationsisacceleratedbytheuseoftheGPU.TheGPUisspecializedintheprocessingofgraphics-relatedinstructionsandcan,therefore,delivermuchbetterperformancethantheCPUwhenitcomestographics.Forexample,CSS3animationsinmodernwebbrowsersareacceleratedbytheGPU,whiletheCPUperformsJavaScriptanimations.Inthepast,theonlywaytoachievesomeanimationswasviaJavaScript.Buttoday,weshouldavoidusingthemwhenpossibleanduseCSS3insteadbecauseitwillhelpustoachievegreatwebperformance.

Inrecentyears,accesstotheGPUhasbeenaddedtobrowsersviatheWebGLAPI.ThisAPIallowswebdeveloperstocreate3DgamesandotherhighlyvisualapplicationsbyusingthepoweroftheGPU.

Framespersecond(FPS)

Wewillnotgointomuchdetailabouttheperformanceof3Dapplicationsbecauseitisareallyextensivefieldandwecouldwriteanentirebooktalkingaboutit.However,wewillmentionanimportantconceptthatcanbeappliedtoanykindofwebapplication:framespersecond(FPS)orframerate.Whenawebapplicationisdisplayedonscreen,itisdoneatanumberofimages(frames)persecond.Alowframeratecanbedetrimentaltotheoveralluserexperiencewhenperceivedbytheusers.Alotofresearchhasbeencarriedoutonthistopic,and60framespersecondseemstobetheoptimumframerateforagreatuserexperience.

Wheneverwedevelopawebapplication,weshouldtakealookattheframerateandtrytopreventitfromdroppingbelow40FPS.Thisisespeciallyimportantduringanimationsanduseractions.

Anopensourcelibrarycalledstats.jscanhelpustoseetheframeratewhiledevelopingawebapplication.ThislibrarycanbedownloadedfromGitHubathttps://github.com/mrdoob/stats.js/.Weneedtodownloadthelibraryandloaditinawebpage.Wecanthenloadthefollowingcodesnippetbyaddinganewfileorjustexecuteitinthedeveloperconsole:

varstats=newStats();

stats.setMode(1);//0:fps,1:ms

//positionoftheframeratecounter(aligntop-left)

stats.domElement.style.position='absolute';

stats.domElement.style.left='0px';

stats.domElement.style.top='0px';

document.body.appendChild(stats.domElement);

varupdate=function(){

stats.begin();

//monitoredcodegoeshere

Page 254: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

stats.end();

requestAnimationFrame(update);

};

requestAnimationFrame(update);

Ifeverythinggoeswell,wewillbeabletoseetheframeratecounterinthetop-leftcornerofthescreen.ClickingonitwillswitchfromtheFPSviewtothemillisecond(MS)view:

TheFPSviewdisplaystheframesrenderedinthelastsecond.Thehigherthisnumberis,thebetter.TheMSviewdisplaysthemillisecondsneededtorenderaframe.Thelowerthisnumberis,thebetter.

Note

SomeadvancedWebGLapplicationsmayrequireanin-depthperformanceanalysis.Forsuchcases,ChromeprovidestheTraceEventProfilingTool.Ifyouwishtolearnmoreaboutthistool,visittheofficialpageathttps://www.chromium.org/developers/how-tos/trace-event-profiling-tool.

Page 255: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CPUperformanceanalysisToanalyzetheusageoftheprocessingtime,wewilltakealookattheexecutionpathofourapplication.Wewillexamineeachofthefunctionsinvokedandhowlongittakestocompletetheirexecution.WecanaccessallthisinformationbyopeningtheChromedevelopertools'Profilestab:

Inthistab,wecanselectCollectJavaScriptCPUProfileandthenclickontheStartbuttontostartrecordingtheCPUusage.BeingabletoselectwhenwewanttostartandstoprecordingtheCPUusagehelpsusselectthespecificfunctionsthatwewanttoanalyze.If,forexample,wewanttoanalyzeafunctionnamedfoo,allweneedtodoisstartrecordingtheCPUusage,invokethefoofunctionandstoprecording.Atimelineliketheoneinthefollowingscreenshotwillthenbedisplayed:

Thetimelinedisplays(horizontally)thefunctionsinvokedinthechronologicalorder.Ifthe

Page 256: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

functioninvokesotherfunctions,thefunction'scall-stackisdisplayedvertically.Whenwehoveroveroneofthesefunctions,wewillbeabletoseeitsdetailsinthebottom-leftcornerofthetimeline:

Thedetailsincludethefollowinginformation:

Name:Thenameofthefunction.Selftime:Thetimespentonthecompletionofthecurrentinvocationofthefunction.Wewilltakeintoaccountthetimespentintheexecutionofthestatementswithinthefunction,notincludinganyfunctionsthatitcalled.Totaltime:Thetotaltimespentonthecompletionofthecurrentinvocationofthefunction.Wewilltakeintoaccountthetimespentintheexecutionofthestatementswithinthefunction,includingfunctionsthatitcalled.Aggregatedselftime:Thetimeforallinvocationsofthefunctionacrosstherecording,notincludingfunctionscalledbythisfunction.Aggregatedtotaltime:Thetimeforallinvocationsofthefunctionacrosstherecording,includingfunctionscalledbythisfunction.

Aswesawinthepreviouschapter,alltheJavaScriptcodeisexecutedinonesinglethreadatruntime.Forthisreason,whenafunctionisexecuted,nootherfunctionwillbeexecuted.Sometimes,theexecutionofafunctiontakestoolongtobecompleted,andtheapplicationbecomesunresponsive.WecanusetheCPUprofilereporttoidentifywhichfunctionsareconsumingtoomuchprocessingtime.Oncewehaveidentifiedthesefunctions,wecanrefactorandthentotrytoimprovetheapplicationresponsiveness.Somecommon

Page 257: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

improvementsincludeusinganasynchronousexecutionflowwhenpossibleandreducingthesizeofthefunctions.

Page 258: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MemoryperformanceanalysisWhenwedeclareavariable,itisallocatedintheRAM.Sometimeafterthevariableisoutofthescope,itisclearedfrommemorybythegarbagecollector.Sometimes,wecangenerateascenarioinwhichavariablenevergoesoutofscope.Ifthevariablenevergoesoutofscope,itwillneverbeclearedfrommemory.Thiscaneventuallyleadtosomeseriousmemoryleakingissues.Amemoryleakisthecontinuouslossofavailablememory.

Whendealingwithmemoryleaks,wecantakeadvantageoftheGoogleChromedevelopertoolstoidentifytherootcauseoftheproblemwithease.

Thefirstthingthatwemightwonderiswhetherourapplicationhasmemoryleaksornot.Wecanfindoutbyvisitingthetimelinetabandclickingonthetop-lefticontostartrecordingtheresourceusage.Oncewestoprecording,atimelinegraphliketheoneinthefollowingscreenshotwillbedisplayed:

Inthetimeline,wecanselectMemorytoseethememoryusage(UsedJSHeap)overtime(thebluelineintheimage).Intheprecedingexample,wecanseeanotabledroptowardstheendoftheline.Thisisagoodsignbecauseitindicatesthatthemajorityoftheusedmemoryhasbeenclearedwhenthepagehasfinishedloading.

Thememoryleakscanalsotakeplaceafterloading;inthatcase,wecanusetheapplicationforawhileandobservehowthememoryusagevariesinthegraphtoidentifythecauseoftheleak.

Analternativewaytodetectmemoryleaksisbyobservingthememoryallocations.WecanaccessthisinformationbyrecordingtheheapallocationsintheProfilestab:

Page 259: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thereportwillbedisplayedafterwehaverecordedsomeusageoftheresources.WecandothisbyclickingontheStartandStopbuttons.Thememoryallocationreportwilldisplayatimelineliketheoneinthefollowingscreenshot.Eachofthebluelinesisamemoryallocationthattookplaceduringtherecordedperiod.Theheightofthelinerepresentstheamountofmemoryused.Asyoucansee,thememoryisalmostclearedcompletelyaroundtheeighthsecond:

Ifweclickononeofthebluelines,wewillbeabletonavigatethroughallthevariablesthatwerestoredinmemorywhentheallocationtookplaceandexaminetheirvalues.ItisalsopossibletotakeamemorysnapshotatanygivenpointfromtheProfilestab:

Thisfeatureisparticularlyusefulwhenwearedebuggingandwewanttoseethememory

Page 260: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

usageataparticularbreakpoint.Thememorysnapshotworkslikethedetailsviewinthepreviouslyexplainedallocationsview:

Asyoucanseeintheprecedingscreenshot,thememorysnapshotallowsustonavigatethroughallthevariablesthatwerestoredinmemorywhenthesnapshotwastakenandexaminetheirvalues.

Page 261: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThegarbagecollectorPrograminglanguageswithalowlevelofabstractionhavelow-levelmemorymanagementmechanisms.Ontheotherhand,inlanguageswithahigherlevelofabstraction,suchasC#orJavaScript,thememoryisautomaticallyallocatedandfreedbyaprocessknownasthegarbagecollector.

TheJavaScriptgarbagecollectordoesagreatjobwhenitcomestomemorymanagement,butitdoesn'tmeanthatwedon'tneedtocareaboutmemorymanagement.

Independentofwhichprogramminglanguageweareworkingwith,thememorylifecycleprettymuchfollowsthesamepattern:

AllocatethememoryyouneedUsethememory(read/write)Releasetheallocatedmemorywhenitisnotneededanymore

Thegarbagecollectorwilltrytoreleasetheallocatedmemorywhenisnotneededanymoreusingavariationofanalgorithmknownasthemark-and-sweepalgorithm.Thegarbagecollectorperformsperiodicalscanstoidentifyobjectsthatareoutofthescopeandcanbefreedfromthememory.Thescanisdividedintwophases:thefirstoneisknownasmarkbecausethegarbagecollectorwillflagormarktheitemsthatcanbefreedfromthememory.Duringthesecondphase,knownassweep,thegarbagecollectorwillfreethememoryconsumedbytheitemsmarkedinthepreviousphase.

Thegarbagecollectorisusuallyabletoidentifywhenanitemcanbeclearedfromthememory;butwe,asdevelopers,musttrytoensurethatobjectsgetoutofscopewhenwedon'tneedthemanymore.Ifavariablenevergetsoutofthescope,itwillbeallocatedinmemoryforever,potentiallyleadingtoaseverememoryleakissue.

Thenumberofreferencespointingtoaniteminmemorywillpreventitfrombeingfreedfrommemory.Forthisreason,mostcasesofmemoryleakscanbefixedbyensuringthattherearenopermanentreferencestovariables.Hereareafewrulesthatcanhelpustopreventpotentialmemoryleakissues:

Remembertoclearintervalswhenyoudon'tneedthemanymore.Remembertocleareventlistenerswhenyoudon'tneedthemanymore.Rememberthatwhenyoucreateaclosure,theinnerfunctionwillrememberthecontextinwhichitwasdeclared.Thismeansthattherewillbesomeextraitemsallocatedinmemory.Rememberthatwhenusingobjectcomposition,ifcircularreferencesarecreated,youcanenduphavingsomevariablesthatwillneverbeclearedfrommemory.

Page 262: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformanceautomationInthissectionwewillunderstandhowwecanautomatemanyoftheperformanceoptimizationtasks,fromconcatenationandcompressionofcontentstotheautomationoftheperformancemonitoringandperformancetestingprocesses.

Page 263: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformanceoptimizationautomationAfteranalyzingtheperformanceofourapplication,wewillstartworkingonsomeperformanceoptimizations.Manyoftheseoptimizationsinvolvetheconcatenationandcompressionofsomeoftheapplication'scomponents.Theproblemwithcompressedcomponentsisthattheyaremorecomplicatedtodebugandmaintain.Wewillalsohavetocreateanewversionoftheconcatenatedandcompressedcontentseverytimeoneoftheoriginalcomponents(notconcatenatedandnotcompressed)changes.Astheseincludemanyhighlyrepetitivetasks,wecanusethetaskrunnerGulptoperformmanyofthesetasksforus.Wecanfindonlinepluginsthatwillallowustoconcatenateandcompresscomponents,optimizeimages,generateacachemanifest,andperformmanyotherperformanceoptimizationtasks.

Note

IfyouwouldliketolearnmoreaboutGulp,refertoChapter2,AutomatingYourDevelopmentWorkflow.

Page 264: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformancemonitoringautomationWehaveseenthatwecanautomatemanyoftheperformanceoptimizationtasksusingtheGulptaskrunner.Inasimilarway,wecanalsoautomatetheperformancemonitoringprocess.

Inordertomonitortheperformanceofanexistingapplication,wewillneedtocollectsomedatathatwillallowustocomparetheapplicationperformanceovertime.Dependingonhowwecollectthedata,wecanidentifythreedifferenttypesofperformancemonitoring:

Realusermonitoring(RUM):Thisisatypeofsolutionusedtocaptureperformancedatafromrealuservisits.ThecollectionofdataisperformedbyasmallJavaScriptcodesnippetloadedinthebrowser.Thistypeofsolutioncanhelpustocollectdataanddiscoverperformancetrendsandpatterns.Simulatedbrowsers:Thistypeofsolutionisusedtocaptureperformancedatafromsimulatedbrowsers.Thisisthemosteconomicoption,butitislimitedbecausesimulatedbrowserscannotofferasaccuratearepresentationoftherealuserexperience.Real-browsermonitoring:Thisisusedtocapturetheperformancedataofrealbrowsers.Thisinformationprovidesamoreaccuraterepresentationoftherealuserexperience,asthedataiscollectedusingexactlywhatauserwouldseeiftheyvisitedthesitewiththegivenenvironment(browser,geographiclocation,andnetworkthroughput).

InChapter2,AutomatingYourDevelopmentWorkflow,wesawhowtoconfigureaGulptaskthatusedtheKarmatestrunnertoexecuteatestsuiteinaheadlessbrowserknownasPhantomJS.

PhantomJSisasimulatedbrowserthatcanbeconfiguredtogenerateHTTPArchive(HAR)files.AHARfileusesacommonformatforrecordingHTTPtracinginformation.Thisfilecontainsavarietyofinformation,butforourpurposes,ithasarecordofeachobjectbeingloadedbyabrowser.

TherearemultiplescriptsavailableonlinethatshowcasehowtocollectthedataandreformatitusingthePhantomJSAPI.Oneoftheexamples,netsniff.js,exportsthenetworktrafficinHARformat.Thenetsniff.jsfile(andotherexamples)canbefoundathttps://github.com/ariya/phantomjs/blob/master/examples/netsniff.js.

OncewehavegeneratedtheHARfiles,wecanuseanotherapplicationtoseethecollectedperformanceinformationonavisualtimeline.ThisapplicationiscalledHARviewer,anditcanbefoundathttps://github.com/janodvarko/harviewer.

Alternatively,wecouldwriteacustomscriptorGulptasktoreadtheHARfilesandbreaktheautomatedbuildiftheapplicationperformancedoesn'tmeetourneeds.

ItisalsopossibletoconfigurePhantomJStoruntheYSlowperformanceanalysisreportandintegrateitwiththeautomatedbuild.TolearnmoreaboutPhantomJSandperformancemonitoring,refertotheofficialdocumentationathttp://phantomjs.org/network-

Page 265: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

monitoring.html.

Note

IfyouareconsideringusingRUM,takealookattheNewRelicsolutionsathttp://newrelic.com/,orGoogleAnalyticsathttp://www.google.com/analytics/.

Page 266: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PerformancetestingautomationAnotherwaytoimprovetheperformanceofanapplicationistowriteautomatedperformancetests.Thesetestscanbeusedtoguaranteethatthesystemmeetsasetofperformancegoals.Therearemultipletypesofperformancetesting,butsomeofthemostcommononesincludethefollowing:

Loadtesting:Thisisthemostbasicformofperformancetesting.Wecanusealoadtesttounderstandthebehaviorofthesystemunderaspecificexpectedload(numberofconcurrentusers,numberoftransactions,andduration).Therearemultipletypesofloadtesting:

Stresstesting:Thisisnormallyusedtounderstandthemaximumcapacitylimitsofanapplication.Thiskindoftestdeterminesifanapplicationisabletohandleanextremeloadbyusinganextremeloadforanextendedperiodoftime.

Stresstestingisnotreallyusefulwhenworkingonaclient-sideapplication.However,itcanbereallyhelpfulwhenworkingonaNode.jsapplication,sinceNode.jsapplicationscanhavemanysimultaneoususers.

Soaktesting:Thisisalsoknownasendurancetesting.Thiskindoftestissimilartothestresstest,butinsteadofusinganextremeload,itusestheexpectedloadforanextendedperiodoftime.Itisacommonpracticetocollectmemoryusagedataduringthiskindoftesttodetectpotentialmemoryleaks.Thiskindoftesthelpsustodetectiftheperformancesufferssomekindofdegradationafteranextendedperiodoftime.Spiketesting:Thisisalsosimilartothestresstest,butinsteadofusinganextremetimeloadduringanextendedtimeperiod,itusessuddenintervalsofextremeandexpectedload.Thiskindoftesthelpsustodetermineifanapplicationisabletohandledramaticchangesinload.Configurationtesting:Thisisusedtodeterminetheeffectsofconfigurationchangesontheperformanceandbehaviorofanapplication.Acommonexamplewouldbeexperimentingwithdifferentmethodsofloadbalancing.

Note

ThiskindoftestcanalsobeautomatedbyusingtoolssuchasJMeter(http://jmeter.apache.org)orLocust(http://locust.io).

Page 267: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExceptionhandlingUnderstandinghowtousetheavailableresourcesinanefficientmannerwillhelpustocreatebetterapplications.Inasimilarmanner,understandinghowtohandleruntimeerrorswillhelpustoimprovetheoverallqualityofourapplications.ExceptionhandlinginTypeScriptinvolvesthreemainlanguageelements.

Page 268: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheErrorclassWhenaruntimeerrortakesplace,aninstanceoftheErrorclassisthrown:

thrownewError();

Wecancreatecustomerrorsinacoupleofdifferentways.TheeasiestwaytoachieveitisbypassingastringasargumenttotheErrorclassconstructor:

ThrownewError("Mybasiccustomerror");

Ifweneedmorecustomizableandadvancedcontrolovercustomexceptions,wecanuseinheritancetoachieveit:

moduleCustomException{

exportdeclareclassError{

publicname:string;

publicmessage:string;

publicstack:string;

constructor(message?:string);

}

exportclassExceptionextendsError{

constructor(publicmessage:string){

super(message);

this.name='Exception';

this.message=message;

this.stack=(<any>newError()).stack;

}

toString(){

returnthis.name+':'+this.message;

}

}

}

Intheprecedingcodesnippet,wehavedeclaredaclassnamedError.ThisclassisavailableatruntimebutisnotdeclaredbyTypeScript,sowewillhavetodoitourselves.Then,wehavecreatedanExceptionclass,whichinheritsfromtheErrorclass.

Finally,wecancreatecustomErrorbyinheritingfromourExceptionclass:

classCustomErrorextendsCustomException.Exception{

//...

}

Page 269: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thetry…catchstatementsandthrowstatementsAcatchclausecontainsstatementsthatspecifywhattodoifanexceptionisthrowninthetryblock.Weshouldperformsomeoperationsinthetryblock,andiftheyfail,theprogramexecutionflowwillmovefromthetryblocktothecatchblock.

Additionally,thereisanoptionalblockknownasfinally,whichisexecutedafterboththetryandcatch(iftherewasanexceptionincatch)blocks:

try{

//codethatwewanttowork

thrownewError("Oops!");

}

catch(e){

//codeexecutedifexpectedtoworkfails

console.log(e);

}

finally{

//codeexecutedalwaysaftertryortryandcatch(whenerrors)

console.log("finally!");

}

Itisalsoimportanttomentionthatinthemajorityofprogramminglanguages,includingTypeScript,throwingandcatchingexceptionsisanexpensiveoperationintermsofresourceconsumption.Weshouldusethesestatementsifweneedthem,butsometimesitisnecessarytoavoidthembecausetheycanpotentiallynegativelyaffecttheperformanceofourapplications.Therefore,weshouldkeepinmindthatitisagoodideatoavoidtheuseoftry…catchandthrowstatementsinperformance-criticalfunctionsandloops.

Page 270: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wesawwhatperformanceisandhowtheavailabilityofresourcescaninfluenceit.WealsolookedathowtousesometoolstoanalyzethewayaTypeScriptapplicationusesavailableresources.Thesetoolsallowustospotsomepossibleissues,suchasalowframerate,memoryleaks,andhighloadingtimes.Wehavealsodiscoveredthatwecanautomatemanykindsofperformanceoptimizationtask,aswellastheperformancemonitoringandtestingprocesses.

Inthefollowingchapter,wewillseehowwecanautomatethetestingprocessofourTypeScriptapplicationstoachievegreatapplicationmaintainabilityandreliability.

Page 271: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter7.ApplicationTestingInthischapter,wearegoingtotakealookathowtowriteunittestsforTypeScriptapplications.Wewillseehowtousetoolsandframeworkstofacilitatethetestingprocessofourapplications.

Thecontentsofthischaptercoverthefollowingtopics:

SettingupatestinfrastructureTestingplanningandmethodologiesHowtoworkwithMocha,Chai,andSinon.JSHowtoworkwithtestassertions,specs,andsuitesTestspiesTeststubsTestingonmultipleenvironmentsHowtoworkwithKarmaandPhantomJSEnd-to-endtestingGeneratingtestcoveragereports

Wewillgetstartedbyinstallingsomenecessarythird-partysoftwaredependencies.

Page 272: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SoftwaretestingglossaryAcrossthischapter,wewillusesomeconceptsthatmaynotbefamiliartothosereaderswithoutprevioussoftwaretestingexperience.Let'stakeaquicklookatsomeofthemostpopulartestingconceptsbeforewegetstarted.

Page 273: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AssertionsAnassertionisaconditionthatmustbetestedtoconfirmthatacertainpieceofcodebehavesasexpectedor,inotherwords,toconfirmconformancetoarequirement.

Let'simaginethatweareworkingaspartofoneoftheGoogleChromedevelopmentteamandwehavetoimplementtheJavaScriptMathobject.Ifweareworkingonthepowmethod,therequirementcouldbesomethinglikethefollowing:

"TheMath.pow(base,exponent)functionshouldreturnthebase(thebasenumber)totheexponent(theexponentusedtoraisethebasepower—thatis,base^exponent)."

Withthisinformation,wecouldcreatethefollowingimplementation:

classMath1{

publicstaticpow(base:number,exponent:number){

varresult=base;

for(vari=1;i<exponent;i++){

result=result*base;

}

returnresult;

}

}

Toensurethatthemethodiscorrectlyimplemented,wemusttestitconformswiththerequirement.Ifweanalyzetherequirementsclosely,weshouldidentifyatleasttwonecessaryassertions.

Thefunctionshouldreturnthebasetotheexponent:

varactual=Math1.pow(3,5);

varexpected=243;

varasertion1=(Math1.pow(base1,exponent1)===expected1);

Theexponentisnotusedasthebase(orthebaseisnotusedastheexponent):

varactual=Math1.pow(5,3);

varexpected=125;

varasertion2=(Math1.pow(base2,exponent2)===expected2);

Ifbothassertionsarevalid,thenourcodeadherestotherequirements,andweknowthatitwillworkasexpected:

varisValidCode=(asertion1&&asertion2);

console.log(isValidCode);

Page 274: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SpecsSpecisatermusedbysoftwaredevelopmentengineerstorefertotestspecifications.Atestspecification(nottobeconfusedwithatestplan)isadetailedlistofallthescenariosthatshouldbetested,howtheyshouldbetested,andsoon.Wewillseelaterinthischapterhowwecanuseatestingframeworktodefineatestspec.

Page 275: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestcasesAtestcaseisasetofconditionsusedtodeterminewhetheroneofthefeaturesofanapplicationisworkingasitwasoriginallyestablishedtowork.Wemightwonderwhatthedifferencebetweenatestassertionandatestcaseis.Whileatestassertionisasinglecondition,atestcaseisasetofconditions.Wewillseelaterinthischapterhowwecanuseatestingframeworktodefinetestcases.

Page 276: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SuitesAsuiteisacollectionoftestcases.Whileatestcaseshouldfocusononlyonetestscenario,atestsuitecancontaintestcasesformanytestscenarios.

Page 277: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SpiesSpiesareafeatureprovidedbysometestingframeworks.Theyallowustowrapamethodandrecorditsusage(input,output,numberoftimesinvoked).Whenwewrapafunctionwithaspy,theunderlyingmethod'sfunctionalitydoesnotchange.

Page 278: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DummiesAdummyobjectisanobjectthatispassedaroundduringtheexecutionofatestbutisneveractuallyused.

Page 279: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StubsAstubisafeatureprovidedbysometestingframeworks.Stubsalsoallowustowrapamethodtoobserveitsusage.Unlikespies,whenwewrapafunctionwithastub,theunderlyingmethod'sfunctionalityisreplacedwithanewbehavior.

Page 280: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MocksMocksareoftenconfusedwithstubs.MartinFowleroncewrotethefollowinginanarticletitledMocksAren'tStubs:

InparticularIseethemoften(mocks)confusedwithstubs-acommonhelpertotestingenvironments.Iunderstandthisconfusion-Isawthemassimilarforawhiletoo,butconversationswiththemockdevelopershavesteadilyallowedalittlemockunderstandingtopenetratemytortoiseshellcranium.Thisdifferenceisactuallytwoseparatedifferences.Ontheonehandthereisadifferenceinhowtestresultsareverified:adistinctionbetweenstateverificationandbehaviorverification.Ontheotherhandisawholedifferentphilosophytothewaytestinganddesignplaytogether,whichItermhereastheclassicalandmockiststylesofTestDrivenDevelopment.

Bothmocksandstubsprovidesomesortofinputtothetestcase;but,despitetheirsimilarities,theflowofinformationfromeachisverydifferent:

StubsprovideinputfortheapplicationundertestsothatthetestcanbeperformedonsomethingelseMocksprovideinputtothetesttodecidewhetherthetestshouldpassorfail

Thedifferencebetweenmocksandstubswillbecomecleareraswemovetowardstheendofthischapter.

Page 281: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestcoverageThetermtestcoveragereferstoaunitofmeasurement,whichisusedtoillustratethenumberofportionsofcodeinanapplicationthathavebeentestedviaautomatedtests.Testcoveragecanbeobtainedbyautomaticallygeneratingtestcoveragereports.Towardstheendofthechapter,wewillseehowtocreatesuchreportsusingatoolcalledIstanbul(http://gotwarlost.github.io/istanbul/).

Page 282: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrerequisitesThroughoutthischapter,wewillusesomethird-partytools,includingsomeframeworksandautomationtools.Wewillstartbylookingateachtoolindetail.Beforewegetstarted,weneedtousenpmtocreateapackage.jsonfileinthefolderthatwearegoingtousetoimplementtheexamplesinthischapter.

Let'screateanewfoldernamedappandrunthenpminitcommandinsideittogenerateanewpackage.jsonfile:

npminit

Note

RefertoChapter2,AutomatingYourDevelopmentWorkflowforadditionalhelponnpm.

Page 283: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GulpWewillusetheGulptaskrunnertorunsometasksnecessarytoexecuteourtests.WecaninstallGulpusingnpm:

npminstallgulp-g

Note

Ifyouarenotfamiliarwithtaskrunnersandcontinuousintegrationbuildservers,takealookatChapter2,AutomatingYourDevelopmentWorkflow.

Page 284: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

KarmaKarmaisatestrunner.WewilluseKarmatoautomaticallyexecuteourtests.Thisisusefulbecausesometimestheexecutionofthetestwillnotbestartedbyoneofthemembersofoursoftwaredevelopmentteam.Instead,itwillbetriggeredbyacontinuousintegrationbuildserver(usuallyviaataskrunner).

Karmacanbeusedwithmultipletestingframeworks,thankstotheinstallationofplugins.Let'sinstallKarmausingthefollowingcommand:

npminstall--save-devkarma

WewillalsoinstallanotherKarmapluginthatfacilitatesthecreationoftestcoveragereports:

npminstall--save-devkarma-coverage

Page 285: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IstanbulIstanbulisatoolthatidentifieswhichlinesofourapplicationareprocessedduringtheexecutionoftheautomatedtest.Itcangeneratereportsknownastestcoveragereports.Thesereportscanhelpustogetanideaoftheleveloftestingofaprojectbecausetheyshowwhichlinesofcodewerenotexecutedandapercentagevaluethatrepresentsthefractionoftheapplicationthathasbeentested.Itisrecommendedthatatestcoveragevalueofatleast75percentoftheoverallapplicationshouldbeachieved,whilemanyopensourceprojectstargetatestcoverageof100percent.

Page 286: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MochaMochaisapopularJavaScripttestinglibrarythatfacilitatesthecreationoftestsuites,testcases,andtestspecs.MochacanbeusedtotestTypeScriptinthefrontendandbackend,identifyperformanceissues,andgeneratedifferenttypesoftestreports,amongmanyotherfeatures.

Let'sinstallMochaandtheKarma-Mochapluginusingthefollowingcommand:

npminstall--save-devmochakarma-mocha

Page 287: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChaiChaiisatestassertionlibrarythatsupportstest-drivendevelopment(TDD)andbehavior-drivendevelopment(BDD)teststyles.

Note

WewillseemoreaboutTDDandBDDlaterinthischapter.

ThemaingoalofChaiistoreducetheamountofworknecessarytocreateatestassertionandmakethetestmorereadable.

WecaninstallChaiandtheKarma-Chaipluginusingthefollowingcommand:

npminstall--save-devchaikarma-chai

Page 288: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Sinon.JSSinon.JSisanisolationframeworkthatprovidesuswithasetofAPIs(testspies,stubs,andmocks)thatcanhelpustotestacomponentinisolation.Testingisolatedsoftwarecomponentsisdifficultbecausethereisahighlevelofcouplingbetweenthecomponents.AmockinglibrarysuchasSinon.JScanhelpusisolatethecomponentsinordertotestindividualfeatures.

WecaninstallSinon.JSandtheKarma-Sinonpluginusingthefollowingcommand:

npminstall--save-devsinonkarma-sinon

Page 289: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypedefinitionsTobeabletoworkwiththird-partylibrariesinJavaScriptwithagoodsupport,weneedtoimportthetypedefinitionsofeachlibrary.Wewillusethetsdpackagemanagertoinstallthenecessarytypedefinitions:

tsdinstallmocha--save

tsdinstallchai--save

tsdinstallsinon--save

tsdinstalljquery--save

Note

RefertoChapter2,AutomatingYourDevelopmentWorkflowforadditionalhelpontsd.

Page 290: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PhantomJSPhantomJSisaheadlessbrowser.WecanusePhantomJStorunourtestsinabrowserwithouthavingtoactuallyopenabrowser.Beingabletodothisisusefulforafewreasons;themainoneisthatPhantomJScanbeexecutedviaacommandinterface,anditisreallyeasytointegratewithtaskrunnersandcontinuousintegrationservers.Thesecondreasonisthatnothavingtoopenabrowserpotentiallyreducesthetimerequiredtocompletetheexecutionofthetestssuites.

WeneedtoinstalltheKarmapluginthatwillrunthetestinPhantomJS:

npminstall--save-devphantomjs

npminstall--save-devkarma-phantomjs-launcher

Page 291: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SeleniumandNightwatch.jsSeleniumisatestrunnerbutitwasespeciallydesignedtorunaparticulartypeoftestknownasanend-to-end(E2E)test.

Note

WewilllearnmoreaboutE2Etestinglateronthischapter,sowedon'tneedtoworrytoomuchaboutthistopicfornow.

Thoughwewillseehowtouseseleniumtowardstheendofthechapter,wecaninstallitnow.WewillnotworkwithSeleniumdirectlybecausewearegoingtouseanothertool(knownasNightwatch.js)forE2Etesting,whichwillautomaticallyrunSeleniumforus.

Nightwatch.jsisanautomatedtestingframework,writteninNode.jsforwebapplicationsandwebsites,whichusestheSeleniumWebDriverAPI.Itisacompletebrowserautomation(end-to-end)solution.

WecaninstallNightwatch.jsandSeleniumbyexecutingthefollowingcommands:

npminstall--save-devgulp-nightwatch

npminstallselenium-standalone-g

selenium-standaloneinstall

Note

TheSeleniumstandalonerequirestheJavabinariestobeinstalledinthedevelopmentenvironmentandaccessiblethroughthe$PATHvariable.RefertotheofficialJavadocumentationathttps://www.java.com/en/download/help/index_installing.xmltolearnmoreabouttheJavainstallation.

Page 292: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingplanningandmethodologiesWhenitcomestosoftwaredevelopment,weusuallyhavemanychoices.Everytimewehavetodevelopanewapplication,wecanchoosethetypeofdatabase,thearchitecture,andframeworksthatwewilluse.Notallourchoicesareabouttechnologies.Forexample,wecanalsochooseasoftwaredevelopmentmethodologysuchasextremeprogrammingorscrum.Whenitcomestotesting,therearetwomajorstylesormethodologies:test-drivendevelopment(TDD)andbehavior-drivendevelopment(BDD).

Page 293: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Test-drivendevelopmentTest-drivendevelopmentisatestingmethodologythatfocusesonencouragingdeveloperstowritetestsbeforetheywriteapplicationcode.Usually,theprocessofwritingcodeinTDDconsistsofthefollowingbasicsteps:

1. Writeatestthatfails.2. Runthetestandensurethatitfails(thereisnocodeatthispointsoitshouldfail).3. Writethecodetomakethetestpass.4. Runthetestandensurethatitpasses.5. Runalltheotherteststoensurethatnootherpartsoftheapplicationbreak.6. Repeattheprocess.

ThedifferencebetweenusingTDDornotisreallyamindset.Manydevelopersdon'tlikewritingtests,sochancesarethat'ifweleavetheirimplementationasthelasttaskinthedevelopmentprocess,thetestswillnotimplementedortheapplicationwilljustbepartiallytested.

TDDisrecommendedbecauseiteffectivelyhelpsyouandyourteamtoincreasethetestcoverageofyourapplicationsand,therefore,significantlyreducethenumberofpotentialissues.

Page 294: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Behavior-drivendevelopment(BDD)Behavior-drivendevelopmentappearedafterTDDwiththemissionofbeingarefinedversionofTDD.BDDfocusesonthewaytestsaredescribed(specs)andstatesthatthetestsshouldfocusontheapplicationrequirementsandnotthetestrequirements.Ideally,thiswillencouragedeveloperstothinklessabouttheteststhemselvesandmoreabouttheapplicationasawhole.

Note

TheoriginalarticleinwhichtheBDDprincipleswereintroducedforthefirsttimebyDanNorthisavailableonlineathttp://dannorth.net/introducing-bdd/.

Aswehavealreadyseen,MochaandChaiprovideAPIsfortheTDDandBDDapproaches.Laterinthischapter,wewillfurtherexplorethesetwoapproaches.

RecommendingoneofthesemethodologiesisnottrivialbecauseTDDandBDDarebothreallygoodtestingmethodologies.However,BDDwasdevelopedafterTDDwiththeobjectivetoimproveit,sowecanarguethatBDDhassomeadditionaladvantagesoverTDD.InBDD,thedescriptionofatestfocusesonwhattheapplicationshoulddoandnotwhatthetestcodeistesting.Thiscanhelpthedeveloperstoidentifyteststhatreflectthebehaviordesiredbythecustomer.BDDtestsarethenusedtodocumenttherequirementsofasysteminawaythatcanbeunderstoodandvalidatedbyboththedeveloperandthecustomer.Ontheotherhand,TDDtestscannotbeunderstoodwitheasebythecustomer.

Page 295: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestsplansandtesttypesThetermtestplanissometimesincorrectlyusedtorefertoatestspecification.Whiletestsspecificationsdefinethescenariosthatwillbetestedandhowtheywillbetested,thetestplanisacollectionofallthetestspecsforagivenarea.

Itisrecommendedtocreateanactualplanningdocumentbecauseatestplancaninvolvemanyprocesses,documents,andpractices.Oneofthemaingoalsofatestplanistoidentifyanddefinewhatkindoftestisadequateforaparticularcomponentorsetofcomponentsinanapplication.

Followingarethemostcommonlyusedtesttypes:

Unittests:Theseareusedtotestanisolatedcomponent.Ifthecomponentisnotisolated—orinotherwords,thecomponenthassomedependencies—wewillhavetousesometoolsandpracticessuchasmocksordependencyinjectiontotrytoisolateitasmuchaswecanduringthetest.

Ifitisnotpossibletomanipulatethecomponentdependencies,wewillusespiestofacilitatethecreationoftheunittests.

Ourmaingoalshouldbetoachievethetotalisolationofacomponentwhenitistested.Aunittestshouldalsobefast,andweshouldtrytoavoidinput/output,networkusage,andanyotheroperationthatcouldpotentiallyaffectthespeedofthetest.Partialintegrationtestsandfullintegrationtests:Theseareusedtotestasetofcomponents(partialintegrationtest)ortheentireapplicationasawhole(fullintegrationtest).Inintegration,wewillnormallyuseknowntestdatatofeedthebackendwithinformationthatwillbedisplayedinthefrontend.Wewillthenassertthatthedisplayedinformationiscorrect.Regressiontests:Thesetestsareusedtoverifythatanissuehasbeenfixed.IfweareusingTDDorBDD,wheneverweencounteranissueweshouldcreateaunittestthatreproducestheissue,andthenchangethecode.Bydoingthis,wewillbeabletorunattemptstoreproducepastissuesandensurethateverythingisstillworking.Performance/Loadtests:Thesetestsverifyiftheapplicationmeetsourperformanceexpectations.Wecanuseperformanceteststoverifythatourapplicationwillbeabletohandlemanyconcurrentusersoractivityspikes.Tolearnmoreaboutthistypeoftest,takealookatthepreviouschapter:Chapter6,ApplicationPerformance.End-to-end(E2E)tests:Thesetestsarenotreallydifferentfromfullintegrationtests.ThemaindifferenceisthatinanE2Etestingsession,wewilltrytoemulateanenvironmentalmostidenticaltotherealuserenvironment.WewilluseNightwatch.jsandSeleniumforthispurpose.Useracceptancetests(UAT):Theseareusedsothatthesystemmeetsalltherequirementsoftheenduser.

Page 296: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SettingupatestinfrastructureAswesawpreviouslyinthischapterwhenwetalkedaboutunittests,usually,testingrequiresbeingabletoisolatetheindividualsoftwarecomponentofourapplications.

Inordertobeabletoisolatethecomponentsofourapplication,wewillneedtoadheretosomeprinciples(suchasthedependencyinversionprinciple)thatwillhelpustoincreasethelevelofdecouplingbetweenthecomponents.

WewillnowconfigureatestingenvironmentusingGulpandKarmaandwritesomeautomatedtestusingMochaandChai.Bytheendofthischapter,wewillknowhowwritingunittestscanhelpustoincreasethelevelofdecouplingandisolationbetweenthecomponentsofanapplication,andhowtheycanleadustothedevelopmentofgreatapplications,especiallywhenitcomestomaintainabilityandreliability.

Let'sgetstartedbycreatingthefolderstructureofanewapplication.Wewillcreatetwofoldersinsidetheappfolderthatwecreatedatthebeginningofthischapter.

Let'snamethefirstfoldersourceandthesecondfoldertest.Here,wecanseehowourdirectorytreeshouldlookbytheendofthechapter:

├──app

├──gulpfile.js

├──index.html

├──karma.conf.js

├──nightwatch.json

├──package.json

├──source

│├──calculator_widget.ts

│├──demos.ts

│├──interfaces.d.ts

│├──math_demo.ts

├──style

│└──demo.css

├──test

│├──bdd.test.ts

│├──e2e.test.ts

│├──tdd.test.ts

├──tsd.json

└──typings

Wearegoingtodevelopareallysmallapplicationtobeabletowriteaunittest.Wearegoingtowriteaunittestandanend-to-endtest.

Note

Thesourcecodeoftheentiredemocanbefoundinthecompanioncodesamples.

Oncewehavecompletedourapplication,wewillbeabletoopenitinabrowser,wherewe

Page 297: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

shouldseeaformliketheoneinthefollowingscreenshot.Thisformallowsustofindtheresultofanumber(base)tothepowerofanother(exponent).

Page 298: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BuildingtheapplicationwithGulpWewillgetstartedbycreatinganewgulpfile.jsfileaswedidinChapter2,AutomatingYourDevelopmentWorkflow.Thefirstthingthatwearegoingtodoisimportallthenecessarynodemodules:

vargulp=require("gulp"),

browserify=require("browserify"),

source=require("vinyl-source-stream"),

buffer=require("vinyl-buffer"),

run=require("gulp-run"),

nightwatch=require('gulp-nightwatch'),

tslint=require("gulp-tslint"),

tsc=require("gulp-typescript"),

browserSync=require('browser-sync'),

karma=require("karma").server,

uglify=require("gulp-uglify"),

docco=require("gulp-docco"),

runSequence=require("run-sequence"),

header=require("gulp-header"),

pkg=require(__dirname+"/package.json");

Note

Rememberthatweneedtoinstallallnecessarypackagesbyusingthenpmpackagemanager.Wecantakealookatthepackage.jsonfiletoseeallthedependenciesandtheirrespectiveversions.

ThesecondthingthatwearegoingtodoistocreatesometaskstocompileourTypeScriptcode.Here,weshouldnoticethatwearegoingcompiletheapplicationcodeintothe/build/sourcefolderandtheapplicationtestsintothe/build/testfolder:

vartsProject=tsc.createProject({

removeComments:false,

noImplicitAny:false,

target:"ES5",

module:"commonjs",

declarationFiles:false

});

gulp.task("build-source",function(){

returngulp.src(__dirname+"/source/*.ts")

.pipe(tsc(tsProject))

.pipe(gulp.dest(__dirname+"/build/source/"));

});

ThepreviousGulptaskcompilestheTypeScriptfilesunderthesourcefolderintoJavaScriptfilesthatwillbestoredininsidethebuild/sourcefolder.Weshouldbeabletorunthetaskbyexecutingthefollowingcommand:

gulpbuild-source

Page 299: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

Theprecedingcommandwillfailifnosourcefilesareavailable.Youcancopyprojectsourcefilesfromthecompanionsourcecodeorcontinuereadingthischapterandcreatethefilesasweprogress.

Wewillalsodeclareasecondtasktocompileourunittests,buttheoutputwillbestoredunderthebuild/testfolder:

vartsTestProject=tsc.createProject({

removeComments:false,

noImplicitAny:false,

target:"ES5",

module:"commonjs",

declarationFiles:false

});

gulp.task("build-test",function(){

returngulp.src(__dirname+"/test/*.test.ts")

.pipe(tsc(tsTestProject))

.pipe(gulp.dest(__dirname+"/build/test/"));

});

WeshouldbeabletorunthisnewtaskusingGulpbyusingthefollowingcommand:

gulpbuild-test

OncetheJavaScriptisunderthebuildfolder,weneedtobundletheexternalmodules(asweused{module:"commonjs"}intheprecedingcompilersettings)intobundledlibrariesthatcanbeexecutedinawebbrowser.

Browserifyneedsauniqueentrypointforeachlibrary.Forthisreason,wearegoingtocreatethreetasks—oneforeachbundledlibrary.

Wewillcreateatasktobundletheapplicationitself:

gulp.task("bundle-source",function(){

varb=browserify({

standalone:'demos',

entries:__dirname+"/build/source/demos.js",

debug:true

});

returnb.bundle()

.pipe(source("demos.js"))

.pipe(buffer())

.pipe(gulp.dest(__dirname+"/bundled/source/"));

});

JustlikewedidwiththepreviousGulptasks,wecaninvokethenewtaskbyusingthefollowingcommand:

Page 300: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

gulpbundle-source

Wewillalsocreateanothertasktobundlealltheunittestsinourapplicationintoasinglebundledsuiteoftests:

gulp.task("bundle-test",function(){

varb=browserify({

standalone:'test',

entries:__dirname+"/build/test/bdd.test.js",

debug:true

});

returnb.bundle()

.pipe(source("bdd.test.js"))

.pipe(buffer())

.pipe(gulp.dest(__dirname+"/bundled/test/"));

});

Note

ThecompanioncodehastestsusingboththeTDDandBDDstylesintwoindependentfilesnamedtdd.test.tsandbdd.test.ts.However,intheexamplesinthischapter,wewillonlyfocusontheBDDstyle.

Wecaninvokethenewtaskbyusingthefollowingcommand:

gulpbundle-test

Finally,wewillcreateanothertasktobundlealltheE2EtestsintheapplicationintoasinglebundledE2Etestsuite:

gulp.task("bundle-e2e-test",function(){

varb=browserify({

standalone:'test',

entries:__dirname+"/build/test/e2e.test.js",

debug:true

});

returnb.bundle()

.pipe(source("e2e.test.js"))

.pipe(buffer())

.pipe(gulp.dest(__dirname+"/bundled/e2e-test/"));

});

Wecaninvokethenewtaskbyusingthefollowingcommand:

gulpbundle-e2e-test

Page 301: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RunningtheunittestwithKarmaWehavealreadycoveredthebasicsofKarmainChapter2,AutomatingYourDevelopmentWorkflow.WearegoingtocreateatasktoexecuteKarma:

gulp.task("run-unit-test",function(cb){

karma.start({

configFile:__dirname+"/karma.conf.js",

singleRun:true

},cb);

});

TheKarmataskconfigurationisreallysimplebecausethemajorityoftheconfigurationislocatedinthekarma.conf.jsfile,whichisincludedinthecompanioncode.Let'stakealookattheconfigurationfile:

module.exports=function(config){

'usestrict';

config.set({

basePath:'',

frameworks:['mocha','chai','sinon'],

browsers:['PhantomJS'],

reporters:['progress','coverage'],

coverageReporter:{

type:'lcov',

dir:__dirname+'/coverage/'

},

plugins:[

'karma-coverage',

'karma-mocha',

'karma-chai',

'karma-sinon',

'karma-phantomjs-launcher'

],

preprocessors:{

'**/bundled/test/bdd.test.js':'coverage'

},

files:[

{

pattern:"/bundled/test/bdd.test.js",

included:true

},

{

pattern:"/node_modules/jquery/dist/jquery.min.js",

included:true

},

{

pattern:"/node_modules/bootstrap/dist/js/bootstrap.min.js",

included:true

}

],

client:{

mocha:{

Page 302: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ui:"bdd"

}

},

port:9876,

colors:true,

autoWatch:false,

logLevel:config.DEBUG

});

};

Ifwetakealookattheconfigurationfile,wewillseethatwehaveconfiguredthepathwherethetestsarelocatedandthebrowserthatwewanttousetorunthetest(PhantomJS).Declaringwhatbrowserwewanttouseisnotenough;wealsoneedtoinstallapluginsoKarmacanlaunchthatbrowser.

SincewearegoingtowritetestusingMocha,Chai,andSinon.JS,wehaveloadedthepluginstointegrateKarmawitheachoftheseframeworks.Therearemanyotherpopulartestingframeworks,andthemajorityofthemarecompatiblewithKarmaviatheuseofplugins.

Anotherinterestingsettingintheprecedingconfigurationfileisthecliententry.WeuseittoconfiguretheoptionsofMochaandindicatethatwearegoingtouseaBDDtestingstyle.

WhenKarmaexecutestheMochaunittests,itgeneratesanHTMLpageinternallyandaddsalltherequiredfilesindicatedinthefilesfieldaswellassomefilesindicatedbythepluginsfield.Fortheprecedingexample,KarmawillgenerateanHTMLpagethatwillcontainreference(usingthe<script>tags)toMocha,Chai,andSinon.JS(indicatedbytheplugins)aswellasjQuery,Bootstrap,andthebdd.test.jsfile(indicatedbythefilesfield).

Note

Thecompanionsourcecodeincludesthepackage.jsonfile.Wecanusethisfiletorunthenpminstallcommandanddownloadallthethird-partydependencies(includingjQueryandBootstrap).

Itisimportanttounderstandthatonlyfilesloadedviathefilesfieldwillbeavailableduringthetestexecution,andthatallthefileswillbeloadedusingascripttag.Sometimes,wemayencounterissuesrelatedtomissingfilesorparsingerrors(whenanon-JavaScriptfileisloadedusingascripttag).Wecanhaveabettercontroloverthefileinclusionprocessusingthesettingspattern,included,served,andwatched:

Settings Description

pattern Thepatterntousetomatchfiles.

IfautoWatchistrue,allfilesthathavesetwatchedtotruewillbewatchedfor

Page 303: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

included changes.

served ShouldthefilesbeservedbyKarma'swebserver?

watched Shouldthefilesbeincludedinthebrowserusingthe<script>tag?Wewillusefalseifwewanttoloadthemmanually(forexample,usingRequireJS).

Thekarma.conf.jsfilealsocontainssomesettingstogeneratetestcoveragereports,butwewillskipthosefornowandfocusonthemtowardstheendofthechapter.

Note

Rememberthatyoucanfindallthedetailsabouteachfieldinthekarma.conf.jsfileathttp://karma-runner.github.io/0.8/config/configuration-file.html.

Page 304: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RunningE2EtestswithSeleniumandNightwatch.jsKarma(incombinationwithMocha,Chai,andSinon.JS)isagreattoolwhenitcomestowritingandexecutingunittestsandpartialintegrationtests.However,KarmaisnotthebesttoolwhenitcomestowritingE2Etests.Forthisreason,wewillwriteacollectionofE2Eteststhatwillbewrittenandexecutedusingaseparatesetoftools:SeleniumandNightwatch.js.

ToconfigureNightwatch.js,wewillstartbycreatinganewGulptaskthatwillbeinchargeoftheexecutionoftheE2Etests.WeonlyneedtospecifythelocationofanexternalconfigurationfilenamedNightwatch.js:

gulp.task('run-e2e-test',function(){

returngulp.src('')

.pipe(nightwatch({

configFile:__dirname+'/nightwatch.json'

}));

});

Note

WearegoingtofocusonNightwatch.jsbecauseitisdesignedtoworkwiththemajorityofframeworks;butifyouareworkingwithAngularJS,IwouldrecommendyoutotakealookatProtractor.ProtractorisagreatE2EtestingframeworkthathasahighlevelofintegrationwithAngularJS.

Thenightwatch.jsfilecontainstheentirerequiredconfigurationnecessarytoexecuteourE2Etests.WeneedtospecifythelocationoftheE2EtestsuitesandthebasicSeleniumconfiguration.

WeneedtothinkthatSeleniumismoreorlesslikeKarma;itisatoolthatcanexecuteaunittestinabrowser.ThemaindifferenceisthatSeleniumallowsustowritetestsinawaythatsimulatesmuchbetterhowarealuserwouldbehave.ItisimportanttounderstandthatNightwatch.jsisnotthetooldirectlyinchargeofexecutionofthetest.Nightwatch.jsisaframeworkthathelpstowriteE2EtestsandcancommunicatewithSeleniumtoexecutethetests.

Inthiscase,wewilltellNightwatch.jsnottorunSeleniumforususingthestart_processentryinthenightwatch.jsonconfigurationfile.Thenightwatch.jsonfileshouldlookasfollows:

{

"src_folders":["bundled/e2e-test/"],

"output_folder":"reports",

"selenium":{

"start_process":false

},

"test_settings":{

Page 305: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

"default":{

"silent":true,

"screenshots":{

"enabled":true,

"path":"screenshots"

},

"desiredCapabilities":{

"browserName":"chrome",

"javascriptEnabled":true,

"acceptSslCerts":true

}

},

"phantomjs":{

"desiredCapabilities":{

"browserName":"phantomjs",

"javascriptEnabled":true,

"acceptSslCerts":true,

"phantomjs.binary.path":"./node_modules/phantomjs/bin/phantomjs"

}

},

"chrome":{

"desiredCapabilities":{

"browserName":"chrome",

"javascriptEnabled":true,

"acceptSslCerts":true

}

}

}

}

WewillrunSeleniummanuallyusingtheselenium-standalonenpmpackage(wecanchecktheprerequisitessectionforinstallationdetails):

selenium-standalonestart

BesidesconfiguringSelenium,weneedtoconfigurewhichwebbrowserswearegoingtouseduringtheexecutionofourE2Etestsandtorunthewebapplicationonawebserver.

Note

IfyouwishtolearnmoreaboutalltheavailableNightwatch.jsconfigurationparameters,pleasevisittheofficialdocumentationathttp://nightwatchjs.org/guide#settings-file.

Finally,tobeabletoruntheE2Etest,wewillalsoneedtoruntheapplicationitselfonawebserver.AswesawinChapter2,AutomatingYourDevelopmentWorkflow,wecanusebrowserSyncforthatpurpose;sowewilladdatasktodeploybrowserSync:

gulp.task('serve',function(cb){

browserSync({

port:8080,

server:{

baseDir:"./"

}

});

Page 306: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

gulp.watch([

"./**/*.js",

"./**/*.css",

"./index.html"

],browserSync.reload,cb);

});

Ifonetestisfailingandwedon'tknowwhatiscausingittofail,wewillbeabletotestitmanuallybyrunningtheapplicationinawebbrowser.

Itisimportanttorunthetasksinthecorrectorder.WeneedtoopenaconsoleorterminalandstartSelenium:

selenium-standalonestart

Openanotherconsoleorterminalandrunthefollowingcommands:

gulpbuild-source

gulpbuild-test

gulpbundle-source

gulpbundle-e2e-test

gulpserve

Finally,openathirdconsoleandrunthefollowingcommand:

gulprun-e2e-test

Page 307: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Creatingtestassertions,specs,andsuiteswithMochaandChaiNowthatthetestinfrastructureisready,wewillstartwritingaunittest.WeneedtorememberthatwearegoingtofollowtheBDDdevelopmenttestingstyle,whichmeansthatwewillwritethetestbeforeweactuallywritethecode.

Wewillwriteawebcalculator;becausewewanttokeepitsimple,wewillonlyimplementoneofitsfeatures.Afterdoingsomeanalysis,wehavecomeupwithadesigninterfacethatwillhelpustounderstandtherequirements.Wewilldeclarethefollowinginterfaceintheinterfaces.d.tsfile:

interfaceMathInterface{

PI:number;

pow(base:number,exponent:number);

}

Aswecansee,thecalculatorwillallowustocalculatetheexponentofanumberandtogetthenumberPI.Nowthatweknowtherequirements,wecanstartwritingsomeunittests.Let'screateafilenamedbdd.test.tsandaddthefollowingcode:

///<referencepath="../typings/tsd.d.ts"/>

///<referencepath="../source/interfaces.d.ts"/>

import{MathDemo}from"../source/math_demo";

varexpect=chai.expect;

describe('BDDtestexampleforMathDemoclass\n',()=>{

before(function(){/*invokedoncebeforeALLtests*/});

after(function(){/*invokedonceafterALLtests*/});

beforeEach(function(){/*invokedoncebeforeEACHtest*/});

afterEach(function(){/*invokedoncebeforeEACHtest*/});

it('shouldreturnthecorrectnumericvalueforPI\n',()=>{

varmath:MathInterface=newMathDemo();

expect(math.PI).to.equals(3.14159265359);

expect(math.PI).to.be.a('number');

});

//...

});

Intheprecedingcodesnippet,wehaveimportedthenecessarytypedefinitionfilesandanexternalmodulenamedMathDemo.ThisexternalmodulewilldeclaretheMathDemoclass,whichwillimplementtheMathInterfacethatweareabouttotest.

Wecanalsoseeashortcutforexpect,sowedon'tneedtowritechai.expecteverytimewe

Page 308: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

needtoinvokeexpect:

varexpect=chai.expect;

Justbelowtheshortcutwecanfindthefirsttestsuite:

describe('BDDtestexampleforMathDemoclass\n',()=>{

Testsuitesaredeclaredusingthedescribe()functionandareusedtowrapasetofunittests;andtheunitteststhemselvesaredeclaredusingtheit()function:

it('shouldreturnthecorrectnumericvalueforPI\n',()=>{

Insidetheunittest,wecanperformoneormoreassertions.TheChaiassertionsprovideeasilyreadablecodethankstotheusageofachainablestyle:

expect(math.PI).to.equals(3.14159265359);

expect(math.PI).to.be.a('number');

Therearecasesinwhichwewillnoticethatwearerepeatingacertaintestinitializationlogicacrossmultipleunittestswithinatestsuite.Therearesomehelperfunctionsthatwecanusetoavoidcodeduplication.

Thebefore()functionwillbeinvokedbeforeanytestinthesuitecaseisexecuted.Theafter()functionwillbeexecutedafterallthetestsinthetestsuitehavebeenexecuted:

before(function(){/*invokedoncebeforeALLtests*/});

after(function(){/*invokedonceafterALLtests*/});

ThebeforeEach()functionisexecutedonce(beforethetestisexecuted)foreachtestinthetestsuite,whiletheafterEach()functionisexecutedonce(afterthetestisexecuted)foreachtestinthetestsuite:

beforeEach(function(){/*invokedoncebeforeEACHtest*/});

afterEach(function(){/*invokedoncebeforeEACHtest*/});

Ifwerunthetestatthispoint,itwillfailbecausethefeaturebeingtested(PI)isnotimplemented.Let'screateafilenamedmath_demo.tsandaddthefollowingcode:

///<referencepath="./interfaces.d.ts"/>

classMathDemoimplementsMathInterface{

publicPI:number;

constructor(){

this.PI=3.14159265359;

}

//...

}

export{MathDemo};

Page 309: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IfweexecutethetestwithKarma,itshouldpasswithouterrors.Itisimportanttorunthetasksinthecorrectorder.Todothis,weneedtoopenaconsoleorterminalandrunthefollowingcommands:

gulpbuild-source

gulpbuild-test

gulpbundle-source

gulpbundle-test

Finally,wecanruntheunittestsusingthefollowingcommand:

gulprun-unit-test

TherewasanotherrequirementintheMathInterfaceinterface,sowearegoingtorepeattheentireBDDprocessoncemore;butthistime,wewilltestafunctionnamedpowinsteadofaproperty.Wewillstartbyaddinganewtesttothetestsuitethatwehavepreciouslycreated:

it('shouldreturnthecorrectnumericvalueforpow\n',()=>{

varmath:MathInterface=newMathDemo();

varresult=math.pow(3,5);

varexpected=243;

expect(result).to.be.a('number');

expect(result).to.equal(expected);

});

AswecanseeinthepreviouslydeclaredMathInterfaceinterface,thefunctionthatwearegoingtotestisnamedpowandtakestwonumericarguments.SowehavecreatedatestthatwillcreateanewinstanceofMathDemoandinvokeitspowmethod,passingthenumericvalues3and5asarguments.Theexpectedvalueofcalculating3*3*3*3*3is243;forthisreason,wehaveassertedthatthepow()functionreturnsanumericvalueanditsvalueis243.

Atthispoint,theprecedingtestwillfailbecausethepowmethodhasnotbeenimplemented.Let'sreturntothemath_demo.tsfileandimplementthepowmethod:

///<referencepath="./interfaces.d.ts"/>

classMathDemoimplementsMathInterface{

publicPI:number;

constructor(){

this.PI=3.14159265359;

}

publicpow(base:number,exponent:number){

varresult=base;

for(vari=1;i<exponent;i++){

result=result*base;

}

returnresult;

}

//...

}

Page 310: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

export{MathDemo};

Ifwerunthetestsagain,wewillbeabletoseethenumberofteststhathavebeenexecuted,howmanyofthemhavefailed,andhowlongittooktofinishtheexecutionofallthetests:

Executed2of2SUCCESS(0.007secs/0.008secs)

Page 311: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheasynchronouscodeInChapter3,WorkingwithFunctions,welearnedhowtoworkwithasynchronouscode;andinChapter6,ApplicationPerformance,wesawthatusingasynchronouscodeisoneofthegoldenrulesofwebapplicationperformance.Weshouldaimtowriteasynchronouscodeasmuchaswecan,andforthisreason,itisimportanttolearnhowtotestasynchronouscode.

Let'swriteanasynchronousversionofthepowfunctiontodemonstratehowwecantestanasynchronousfunction.Wewillstartwiththerequirements:

interfaceMathInterface{

//..

powAsync(base:number,exponent:number,cb:(result:number)=>void);

}

WeneedtoimplementafunctionnamedpowAsync,whichtakestwonumericvaluesasparameters(justlikebefore)andacallbackfunction.Thetestfortheasynchronousversionisalmostidenticaltothetestthatwewroteforthesynchronousfunction:

it('shouldreturnthecorrectnumericvalueforpow(async)\n',(done)=>{

varmath:MathInterface=newMathDemo();

math.powAsync(3,5,function(result){

varexpected=243;

expect(result).to.be.a('number');

expect(result).to.equal(expected);

done();//invokedone()insideyourcallbackorfulfilledpromises

});

});

Themainthingthatweneedtonoticeisthat,thistime,thecallbackpassedtotheitmethodreceivesanargumentnameddone.Theargumentisafunctionthatweneedtoexecutetoindicatethatthetestexecutionisfinished.

Bydefault,theitmethodwaitsforthecallbacktoreturn,butwhentestingasynchronouscode,thefunctionmayreturnbeforethetestexecutionisfinished:

publicpowAsyncSlow(base:number,exponent:number,cb:(result:number)=>

void){

vardelay=45;//ms

setTimeout(()=>{

varresult=this.pow(base,exponent);

cb(result);

},delay);

}

Whentestingasynchronouscode,Mochawillconsiderthetestasfailed(timeout)ifittakesmorethan2,000millisecondstoinvokethedonefunction.Thetimelimitbeforeatimeoutcanbeconfigured,ascanbewarningsforslowfunctions.

Note

Page 312: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Mocharecommendsthat,whenafunctiontakesmorethan40milliseconds,weshouldconsiderinvestigatinghowtoimproveitsperformance.Ifthefunctionexecutiontakesover100milliseconds,wemustinvestigate.Executiontimesofover2,000millisecondsarenottoleratedbydefault.

Page 313: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AssertingexceptionsAssertingthetypesorvaluesofvariablesisstraightforward,aswehavebeenabletoexploreinthepreviousexamples;butthereisonescenariothatperhapsisnotasintuitiveasthepreviousone.Thisscenarioistestingforanexception.

Let'saddanewmethodtotheMathInterfaceinterfacewiththeonlypurposeofillustratinghowtotestforanexception:

interfaceMathInterface{

//...

bad(foo?:any):void;

}

Thebadmethodthrowsanexceptionwhenitisinvokedwithanon-numericargument:

publicbad(foo?:any){

if(isNaN(foo)){

thrownewError("Error!");

}

else{

//...

}

}

Inthefollowingtest,wecanseehowwecanuseChai'sexpectAPItoassertthatanexceptionisthrown:

it('shouldthrowanexceptionwhennoparameterspassed\n',()=>{

varmath:MathInterface=newMathDemo();

varthrowsF=function(){math.bad(/*missingargs*/)};

expect(throwsF).to.throw(Error);

});

Note

Ifyouwishtolearnmoreaboutassertions,visittheChaiofficialdocumentationavailableathttp://chaijs.com/api/bdd/.

Page 314: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TDDversusBDDwithMochaandChaiTDDandBDDfollowmanyofthesameprinciplesbuthavesomedifferencesintheirstyle.Whilethesetwostylesprovidethesamefunctionality,BDDisconsideredtobeeasiertoreadbymanyofthemembersofasoftwaredevelopmentteam(notjustdevelopers).

Thefollowingtablecomparesthenamingandstyleofsuites,tests,andassertionsbetweentheTDDandBBDstyles:

TDD BDD

suite describe

setup before

teardown after

suiteSetup beforeEach

suiteTeardown afterEach

test it

assert.equal(math.PI,3.14159265359); expect(math.PI).to.equals(3.14159265359);

Note

Inthecompanioncodesamples,youwillfindalltheexamplesinthischapterfollowingboththeTDDandBDDstyles.

Page 315: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestspiesandstubswithSinon.JSWehavebeenworkingontheMathDemoclass.Wehaveimplementedandtesteditsfeaturesusingunittestsandassertions.NowwearegoingtocreatealittlewebwidgetthatwillinternallyusetheMathDemoclasstoperformamathematicaloperation.WecanthinkofthisnewclassasagraphicaluserinterfacefortheMathDemoclass.WeneedthefollowingHTML:

<divid="widget">

<inputtype="text"id="base"/>

<inputtype="text"id="exponent"/>

<inputtype="text"id="result"/>

<buttonid="submit"type="submit">Submit</button>

</div>

Note

Inthecompanioncode,theHTMLcodecontainsmoreattributes,suchasCSSclasses;buttheybeenhaveremovedhereforclarity.

Let'screateafilenamedcalculator_widget.tsunderthesourcedirectory.WearegoingtostoretheHTMLcodeinastringvariablelocatedinthescopeofthewebwidget.ThenewclasswillbecalledCalculatorWidget,anditwillimplementtheCalculatorWidgetInterfaceinterface:

interfaceCalculatorWidgetInterface{

render(id:string);

onSubmit():void;

}

WeshouldwritetheunittestbeforeweimplementtheCalculatorWidgetclass,butthistimewewillbreaktheBDDrulesinanattempttofacilitatetheunderstandingofstubsandspies:

///<referencepath="./interfaces.d.ts"/>

///<referencepath="../typings/tsd.d.ts"/>

vartemplate='HTML...';

classCalculatorWidgetimplementsCalculatorWidgetInterface{

private_math:MathInterface;

private$base:JQuery;

private$exponent:JQuery;

private$result:JQuery;

private$btn:JQuery;

constructor(math:MathInterface){

if(math==null)thrownewError("Argumentnullexception!");

this._math=math;

}

publicrender(id:string){

$(id).html(template);

Page 316: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.$base=$("#base");

this.$exponent=$("#exponent");

this.$result=$("#result");

this.$btn=$("#submit");

this.$btn.on("click",(e)=>{

this.onSubmit();

});

}

publiconSubmit(){

varbase=parseInt(this.$base.val());

varexponent=parseInt(this.$exponent.val());

if(isNaN(base)||isNaN(exponent)){

alert("Baseandexponentmustbeanumber!");

}

else{

this.$result.val(this._math.pow(base,exponent));

}

}

}

export{CalculatorWidget};

Aswecansee,wehavedefinedavariablethatcontainstheHTMLthatwepreviouslyexaminedbutitisnotdisplayedforbrevity.AnewclassnamedCalculatorWidgetisalsodefinedtogetherwiththeclassconstructor.Wecanobservethattheclasshastwoproperties:avariablenamed_domandanimplementationofMathInterfacenamed_math.WearedependingonaninterfacebecauseaswesawinChapter4,Object-OrientedProgrammingwithTypeScript,itisagoodpractice(dependencyinversionprinciple)todoso.

NoticethattheclassconstructortakesanimplementationofMathInterfaceasitsonlyargument.Passingthedependenciesofacomponentviaitsconstructorisalsoagoodpracticeandisusedtoreducethecouplingbetweencomponents.

ThefirstmethodintheclassisnamedrenderandtakestheID(string)ofanHTMLelementasitsonlyargument.TheIDisusedtoselectthenodethatmatchestheIDusingajQueryselector.Onceithasbeenselected,theHTMLthatwepreviouslyexaminedisinsertedintotheselectednode.WecansaythatthecomponentisinchargeofrenderingitsownHTMLandcanbereusedeasilyjustbychangingitscontainer.Thisishowwebwidgetsusuallywork:theyareindependentcomponentsthatcanbeconsideredasreusablestandaloneapplicationswithinaparentapplicationthatisnomorethanjustacollectionofwebwidgets.

AfterrenderingtheHTML,therendermethodcreatesshortcutsforeachcomponentofthewidget'sformandinitializesaclickeventlistener:

publicrender(id:string){

$(id).html(template);

this._dom.$base=$("#base");

this._dom.$exponent=$("#exponent");

this._dom.$result=$("#result");

this._dom.$btn=$("#submit");

Page 317: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this._dom.$btn.on("click",(e)=>{

this.onSubmit();

});

}

Whenauserclicksonthebuttonwithidequalstosubmit,aneventistriggered,andtheeventlistenerinvokestheonSubmitfunctionthatwecanfindinthefollowingcodesnippet.Thisfunctionwillreadthevaluesforbaseandexponentusingtheshortcutspreviouslydeclared:

publiconSubmit(){

varbase=parseInt(this._dom.$base.val());

varexponent=parseInt(this._dom.$exponent.val());

if(isNaN(base)||isNaN(exponent)){

alert("Baseandexponentmustbeanumber!");

}

else{

this._dom.$result.val(this._math.pow(base,exponent));

}

}

}

Ifthevaluesoftheinputs(baseandexponent)arenotnumericvalues,analertmessageisdisplayedtoprovidetheuserswitherrorfeedback.Ifthevaluesarenumeric,thepowmethodoftheMathDemoclassisinvoked,andtheresultisassignedtotheresultfieldvalueviaoneofthepreviouslycreatedshortcuts.

Writingunittestscanbecomeacomplextaskwhenthecomponentsbeingtestedarehighlycoupledwithothercomponents.Intheprevioussection,wetriedtofollowsomegoodpracticessuchasthedependencyinversionprincipleorinjectingdependenciesviatheconstructorofthedependent;butsometimes,evenwhenusinggoodpractices,wewillhavetodealwithhighlycoupledcode.

Spies,mocks,andstubscanhelpustotakeawaysomeofthepaincausedbyhighlycoupledcomponents.Thesefeaturescanalsohelpustoidentifytherootcauseofanissue.Ifwereplaceallthedependenciesofacomponentwithstubsandatestfail,wewillknowthattheissueislocatedinthecomponentbeingtestedandnotinoneofitsdependencies.

Forexample,theCalculatorWidgetclasshasadependencyontheMathDemoclass.Ifthereisanissueinthecalculatorwebsite,wewillnotbeabletoknowiftherootcauseoftheissueislocatedintheCalculatorWidgetclassortheMathDemoclass.However,ifwewritesomeunittestsfortheCalculatorWidgetclassinisolation(replacingitsMathDemodependencywithastub)andsomeofthetestsfail,wewillknowforsurethattherootcauseoftheissueislocatedintheCalculatorWidgetandnotintheMathDemoclass.

Let'stakealookatsometestexamples.

Page 318: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SpiesWearegoingtostartbytakingalookattheuseofspiesbycreatinganewtestsuite.Thistimewewillusethebefore()andbeforeEach()functions.Whenthebefore()functionisinvoked(beforeanyunittestisexecuted),anewHTMLnodeiscreatedtoholdthewidget'sHTML.

ThebeforeEach()functionisusedtoresetthecontainerbeforeeachtest.Thisway,wecanensurethatanewwidgetiscreatedforeachtestinthetestsuite.Thisisagoodideabecauseitwillpreventonetestfrompotentiallyaffectingtheresultsofanother.

describe('BDDtestexampleforCalculatorWidgetclass\n',()=>{

before(function(){

$("body").append('<divid="widget"/>');

});

beforeEach(function(){

$("#widget").empty();

});

Note

Usually,testingframeworks(regardlessofthelanguageweareworkingwith)won'tallowustocontroltheorderinwhichtheunittestsandtestsuitesareexecuted.Thetestscanevenbeexecutedinparallelbyusingmultiplethreads.Forthisreason,itisimportanttoensurethattheunittestsinourtestsuitesareindependentofeachother.

Nowthatthetestsuiteisready,wecancreateunittestsfortherender()andonSubmit()methods.TheteststartsbythecreationofaninstanceofMathDemo,whichisthenpassedtoCalculatorWidgetconstructortocreateanewinstancenamedcalculator.

TherendermethodistheninvokedtorenderthewidgetinsidetheHTMLnodewiththeIDwidget.TheHTMLnodeshouldbeavailableatthisstagebecauseitwascreatedbythebefore()method.Afterthewidgethasbeenrendered,avalueissetfortheinputswithIDsbaseandexponent.

Thetestspecification(onSubmitshouldbeinvokedwhen#submitisclicked)shouldhelpusunderstandthatwearetestingtheclickevent.WearegoingtouseaspytoobservetheonSubmit()function;so,whenthebuttonwithIDsubmitisclicked,thespywilldetectthattheonSubmit()functionwasinvoked.

Tofinishthetest,wearegoingtotriggeraclickeventonthebuttonwithIDsubmitandassertthattheonSubmit()functionwasactuallyonlyinvokedonce:

it('onSubmitshouldbeinvokedwhen#submitisclicked',()=>{

varmath:MathInterface=newMathDemo();

varcalculator=newCalculatorWidget(math);

calculator.render("#widget");

$('#base').val("2");

Page 319: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

$('#exponent').val("3");

//spyononSubmit

varonSubmitSpy=sinon.spy(calculator,"onSubmit");

$("#submit").trigger("click");

//assertcalculator.onSubmitwasinvokedwhenclickon#submit

expect(onSubmitSpy.called).to.equal(true);

expect(onSubmitSpy.callCount).to.equal(1);

expect($("#result").val()).to.equal("8");

});

Spieswillallowustoperformmanyoperations:fromcheckinghowmanytimesafunctionhasbeeninvokedtocheckingifitwasinvokedusingthenewoperator,orifitwasinvokedwithasetofspecificparameters.

ThelastassertionhelpsusguaranteethatonSubmit()issettingthecorrectresultintheresultinput.

Note

AllthepossibleoperationsaredetailedintheSinon.JSonlinedocumentationfoundathttp://sinonjs.org/docs/#sinonspy.

Page 320: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StubsItmaylooklikewehavealreadytestedtheentireapplicationbynow,butthatisusuallyneverthecase.Let'sanalyzewhatexactlywehavetestedsofar:

WehavetestedtheentireMathDemoclass,andweknowthatitreturnsthecorrectvaluewhenpowisinvokedWeknowthattheCalculatorWidgetclassisrenderingtheHTMLcorrectlyWeknowthattheCalculatorWidgetclassissettingupsomeeventsandreadingsomevaluesfromtheHTMLinputsasexpected

Sofar,wehavecreatedsometestsfortheMathDemoclassandtheCalculatorWidgetclass,butwehaveforgottentotesttheintegrationbetweenthem.

Wehavebeentestingusing2asbaseand3asexponent,butifwewronglyusedthesamevalueasbaseandexponent,wecouldhavemissedonepotentialissue:maybetheCalculatorWidgetclassispassingtheargumentsinincorrectordertotheMathDemoclasswhenthefunctionpow()isinvokedinthebodyoftheonSubmit()function.

Note

Lateroninthischapter,wewillseehowtogenerateakindofreport(atestcoveragereport)thatcanhelpustoidentifyareasofourapplicationthathavenotbeentested.

WecantestthisscenariobyisolatingtheCalculatorWidgetclassfromitsdependencyontheMathDemoclass.Wecanachievethisbyusingastub.Let'stakealookattheupcomingunitteststoseeastubinaction.

Atthebeginningofthemethod,anewinstanceofMathDemoiscreated,andastubisusedagainstitspowmethod.Thestubwillreplacethepowmethodwithanewmethod.Thenewmethodwillassertthattheparametersreceivedareinthecorrectorder:

it('passtherightargstoMath.pow',(done)=>{

varmath:MathInterface=newMathDemo();

//replacepowmethodwithamethodfortesting

sinon.stub(math,"pow",function(a,b){

//assertthatCalculatorWidget.onSubmitinvokes

//math.powwiththerightarguments

expect(a).to.equal(2);

expect(b).to.equal(3);

done();

});

varcalculator=newCalculatorWidget(math);

calculator.render("#widget");

$('#base').val("2");

$('#exponent').val("3");

$("#submit").trigger("click");

Page 321: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

});

Oncethestubisready,anewinstanceoftheCalculatorWidgetclassiscreated,butinsteadofpassinganormalinstanceofMathDemoasitsonlyargument,weareinjectingthestub.Bydoingthis,wearenolongertestingtheMathDemoclass,andwearetestingtheCalculatorWidgetclassinanisolatedenvironment.Thiswouldhavebeenmuchmorecomplicatedwithoutadesignthatfacilitatesreplacingtheclassdependenciesviaaconstructorinjection.

Tofinishthetest,werenderthecalculatorwidget,setthevalueoftheinputswithIDsbaseandexponent,andtriggeraclickonthebuttonwithIDsubmit.TheeventwillinvoketheonSubmitfunction,whichwilltheninvokethepowmethod.Whentheparametersareintheincorrectorder,wewillbeabletobe100percentsureaboutthelocationoftherootcauseofthisissue:theonSubmitfunction.

Page 322: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Creatingend-to-endtestswithNightwatch.jsWritinganE2EtestwithNightwatch.jsisanintuitiveprocess.WeshouldbeabletoreadanE2Etestandbeabletounderstanditevenifitisthefirsttimethatweencounterone.

Ifwetakealookatthefollowingcodesnippet,wewillseethat,oncewehavereachedthepage,thetestwillwait1secondforthebodyofthepagetobevisible.Thetestwillthenwait0.1secondsforsomeelementstobevisible.TheelementscanbeselectedusingCSSselectorsorXPathsyntax.Iftheelementsarevisible,thesetValuemethodwillinsert2inthetextinputwithbaseasIDand3inthetextinputwithexponentasID:

vartest={

'Calculatorpowe2etestexample':function(client){

client

.url('http://localhost:8080/')

.waitForElementVisible('body',1000)

.assert.waitForElementVisible('TypeScriptTesting',100)

.assert.waitForElementVisible('input#base',100)

.assert.waitForElementVisible('input#exponent',100)

.setValue('input#base','2')

.setValue('input#exponent','3')

.click('button#submit')

.pause(100)

.assert.value('input#result','8')

.end();

}

};

export=test;

Thetestwillthenfindthesubmitbuttonandtriggeranon-clickevent.After0.1seconds,thetestassertsthatthecorrectvaluehasbeeninsertedintothetextinputwithresultasID.Wecanseeeachofthesestepsintheconsoleduringthetestexecution.

Wecanrunthetestsusingthefollowingcommand:

gulprun-e2e-test

Note

RememberthatwemustrunthetaskstocompileandbundletheE2EtestsaswellasruntheapplicationinawebserverwithBrowserSyncandexecuteSeleniumbeforebeingabletorunE2Etests.

Page 323: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GeneratingtestcoveragereportsEarlierininthischapter,whenweconfiguredKarma,weaddedsomesettingstogeneratetestcoveragereports.Let'stakealookatthekarma.conf.jsfiletoidentifytestcoverage-relatedconfiguration:

module.exports=function(config){

'usestrict';

config.set({

basePath:'',

frameworks:['mocha','chai','sinon'],

browsers:['PhantomJS'],

reporters:['progress','coverage'],

coverageReporter:{

type:'lcov',

dir:__dirname+'/coverage/'

},

plugins:[

'karma-coverage',

'karma-mocha',

'karma-chai',

'karma-sinon',

'karma-phantomjs-launcher'

],

preprocessors:{

'**/bundled/test/bdd.test.js':'coverage'

},

files:[

{

pattern:"/bundled/test/bdd.test.js",

included:true

},

{

pattern:"/node_modules/jquery/dist/jquery.min.js",

included:true

},

{

pattern:"/node_modules/bootstrap/dist/js/bootstrap.min.js",

included:true

}

],

client:{

mocha:{

ui:"bdd"

}

},

port:9876,

colors:true,

autoWatch:false,

logLevel:config.DEBUG

});

};

Page 324: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Aswecansee,weneedtosetthefolderinwhichthetestcoveragereportwillbestored.Wealsoneedtoaddcoveragetothereporter'ssettingandanewentrynamedcoverageReporttoconfiguretheformatofthereport.

Wecannotforgettoinstallthekarma-coveragepluginusingnpmandaddingareferenceinthekarma.conf.jsunderthepluginsfield.Finally,weneedtoaddcoveragetothepreprocessorfield:

npmkarma-coverage

Togeneratethereport,wejustneedtoexecutetheGulptasksusedtorunalltheunittestsintheapplication.Wecandosobyusingthefollowingcommand:

gulprun-unit-test

Oncetheexecutionofthetesthasbeencompleted,wecanopenthefolderinwhichwedecidedtostorethecoveragereportsandopentheavailableindex.htmlfileinawebbrowser.TheHTMLreportallowsustonavigatetothecoveragestatisticsofaspecificfilebyclickingonthenameofoneofthesourcefiles.

Thereportcanhelpustoidentifywitheasethepartsofourcodethathavenotbeentested(linesarehighlightedinred).Thetestcoveragereportalsocalculatesthenumberoflinestestedagainstthenumberoflinesintheapplication.Aswecanseeintheprecedingscreenshot,only82.24percentofthestatementsaretestedintheexample.

Note

Ifyouwouldliketolearnmoreaboutallthetoolsthatwehavediscussedinthischapter,I

Page 325: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

highlyrecommendcheckingoutthebookBackbone.jsTestingwrittenbyRyanRoemer.

Page 326: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wediscussedsomecoretestingconcepts(includingstubs,spies,suites,andmore).Wealsolookedatthetest-drivendevelopmentandbehavior-drivendevelopmentapproachesandhowtoworkwithsomeoftheleadingJavaScripttestingframeworks,suchasMocha,Chai,Sinon.JS,Karma,Selenium,andNightwatch.js.

Towardstheendofthechapterweexploredhowtotestacrossmultipledevicesandhowtogeneratetestcoveragereports.

Inthenextchapter,wewilllookatdecoratorsandthemetadatareflectionAPI—twoexcitingnewfeaturesintroducedbyTypeScript1.5.

Page 327: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter8.DecoratorsInthischapter,youaregoingtolearnaboutannotationsanddecorators—thetwonewfeaturesbasedonthefutureECMAScript6specification,butwecanusethemtodaywithTypeScript1.5.

Youwilllearnaboutthefollowingtopics:

Annotationsanddecorators:ClassdecoratorsMethoddecoratorsPropertydecoratorsParameterdecoratorsDecoratorfactoryDecoratorswithparameters

ThereflectionmetadataAPI

Page 328: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrerequisitesTheTypeScriptfeaturesinthischapterrequireTypeScript1.5orhigher.WecanuseGulpaswehavedoneinpreviouschapters,butweneedtoensurethatthelatestversionofTypeScriptisusedbythegulp-typescriptpackage.Let'sstartbycreatingapackage.jsonfileandinstallingtherequiredpackages:

npminit

npminstall--save-devgulpgulp-typescripttypescript

npminstall--savereflect-metadata

Oncewehaveinstalledthepackages,wecancreateagulpfile.jsfileandaddanewtasktocompileourcode.

Thefollowingcodesnippetshowstherequiredcompilerconfiguration.ThecompilationtargetmustbeES5andtheemitDecoratorMetadatasettingmustbesetastrue.WealsoneedtospecifythepackagethatprovidestheTypeScriptcompilertoensurethatthelatestversionisused:

vargulp=require("gulp"),

tsc=require("gulp-typescript"),

typescript=require("typescript");

vartsProject=tsc.createProject({

removeComments:false,

noImplicitAny:false,

target:"es5",

module:"commonjs",

declarationFiles:false,

emitDecoratorMetadata:true,

typescript:typescript

});

Oncethecompilersettingsareready,wecanwriteagulptaskusingthegulp-typescriptplugin:

gulp.task("build-source",function(){

returngulp.src(__dirname+"/file.ts")

.pipe(tsc(tsProject))

.js.pipe(gulp.dest(__dirname+"/"));

});

Page 329: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AnnotationsanddecoratorsAnnotationsareawaytoaddmetadatatoclassdeclarations.Themetadatacanthenbeusedbytoolssuchasdependencyinjectioncontainers.

TheannotationsAPIwasproposedbytheGoogleAtScriptteambutannotationsarenotastandard.However,decoratorsareaproposedstandardforECMAScript7byYehudaKatz,toannotateandmodifyclassesandpropertiesatdesigntime.

Annotationsanddecoratorsareprettymuchthesame:

Annotationsanddecoratorsarenearlythesamething.Fromaconsumerperspectivewehaveexactlythesamesyntax.Theonlythingthatdiffersisthatwedon'thavecontroloverhowannotationsareaddedasmetadatatoourcode.Adecoratorisratheraninterfacetobuildsomethingthatendsupasannotation.

Overalongterm,however,wecanjustfocusondecorators,sincethosearearealproposedstandard.AtScriptisTypeScriptandTypeScriptimplementsdecorators.

--"ThedifferencebetweenAnnotationsandDecorators"byPascalPrecht

Wearegoingtousethefollowingclasstoshowcasehowtoworkwithdecorators:

classPerson{

publicname:string;

publicsurname:string;

constructor(name:string,surname:string){

this.name=name;

this.surname=surname;

}

publicsaySomething(something:string):string{

returnthis.name+""+this.surname+"says:"+something;

}

}

Therearefourtypesofdecoratorsthatcanbeusedtoannotate:classes,properties,methods,andparameters.

Page 330: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheclassdecoratorsTheofficialTypeScriptdecoratorproposaldefinesaclassdecoratorasfollows:

Aclassdecoratorfunctionisafunctionthatacceptsaconstructorfunctionasitsargument,andreturnseitherundefined,theprovidedconstructorfunction,oranewconstructorfunction.Returningundefinedisequivalenttoreturningtheprovidedconstructorfunction.

--"DecoratorsProposal–TypeScript"byRonBuckton

Aclassdecoratorisusedtomodifytheconstructorofclassinsomeway.Iftheclassdecoratorreturnsundefined,theoriginalconstructorremainsthesame.Ifthedecoratorreturns,thereturnvaluewillbeusedtooverridetheoriginalclassconstructor.

WearegoingtocreateaclassdecoratornamedlogClass.Wecanstartbydefiningthedecoratorasfollows:

functionlogClass(target:any){

//…

}

Theclassdecoratorabovedoesnothaveanylogicyet,butwecanalreadyapplyittoaclass.Toapplyadecorator,weneedtousetheat(@)symbol:

@logClass

classPerson{

publicname:string;

publicsurname:string;

//...

Ifwehavedeclaredandappliedadecorator,afunctionnamed__decoratewillbegeneratedbytheTypeScriptcompiler,whichwillthencompileourcodeinJavaScript.Wearenotgoingtoexaminetheinternalimplementationofthe__decoratefunction,butweneedtounderstandthatitisusedtoapplyadecoratoratruntime.WecanseeitinactionbyexaminingtheJavaScriptcodethatisgeneratedwhenwecompilethedecoratedPersonclassmentionedpreviously:

varPerson=(function(){

functionPerson(name,surname){

this.name=name;

this.surname=surname;

}

Person.prototype.saySomething=function(something){

returnthis.name+""+this.surname+"says:"+something;

};

Person=__decorate([

logClass

],Person);

returnPerson;

})();

Page 331: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Nowthatweknowhowtheclassdecoratorwillbeinvoked,let'simplementit:

functionlogClass(target:any){

//saveareferencetotheoriginalconstructor

varoriginal=target;

//autilityfunctiontogenerateinstancesofaclass

functionconstruct(constructor,args){

varc:any=function(){

returnconstructor.apply(this,args);

}

c.prototype=constructor.prototype;

returnnewc();

}

//thenewconstructorbehaviour

varf:any=function(...args){

console.log("New:"+original.name);

returnconstruct(original,args);

}

//copyprototypesoinstanceofoperatorstillworks

f.prototype=original.prototype;

//returnnewconstructor(willoverrideoriginal)

returnf;

}

Theclassdecoratortakestheconstructoroftheclassbeingdecoratedasitsonlyargument.Thismeansthattheargument(namedtarget)istheconstructorofthePersonclass.

Thedecoratorstartsbycreatingacopyoftheclassconstructor,thenitdefinesautilityfunction(namedconstruct)thatcanbeusedtogenerateinstancesofaclass.

Decoratorsareusedtoaddsomeextralogicormetadatatothedecoratedelement.Whenwetrytoextendthefunctionalityofafunction(methodsorconstructors),weneedtowraptheoriginalfunctionwithanewfunctionthatcontainstheadditionallogicandinvokestheoriginalfunction.

Intheprecedingdecorator,weaddedextralogictologintheconsole,thenameoftheclasswhenanewinstanceiscreated.Toachievethis,anewclassconstructor(namedf)wasdeclared.Thenewconstructorcontainstheadditionallogicandusestheconstructfunctiontoinvoketheoriginalclassconstructor.

Attheendofthedecorator,theprototypeoftheoriginalconstructorfunctioniscopiedtothenewconstructorfunctiontoensurethattheinstanceofoperatorcontinuestoworkwhenitisappliedtoaninstanceofthedecoratedclass.Finally,thenewconstructorisreturnedandsomecodegeneratedbytheTypeScriptcompilerusesittooverridetheoriginalclassconstructor.

Afterdecoratingtheclassconstructor,anewinstanceiscreated:

Page 332: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

varme=newPerson("Remo","Jansen");

Ondoingso,thefollowingtextappearsintheconsole:

"New:Person"

Page 333: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThemethoddecoratorsTheofficialTypeScriptdecoratorproposaldefinesamethoddecoratorasfollows.

Amethoddecoratorfunctionisafunctionthatacceptsthreearguments:Theobjectthatownstheproperty,thekeyfortheproperty(astringorasymbol),andoptionallythepropertydescriptoroftheproperty.Thefunctionmustreturneitherundefined,theprovidedpropertydescriptor,oranewpropertydescriptor.Returningundefinedisequivalenttoreturningtheprovidedpropertydescriptor.

--"DecoratorsProposal–TypeScript"byRonBuckton

Themethoddecoratorisreallysimilartotheclassdecoratorbutitisusedtooverrideamethod,asopposedtousingittooverridetheconstructorofaclass.

Ifthemethoddecoratorreturnsavaluedifferentfromundefined,thereturnedvaluewillbeusedtooverridethepropertydescriptorofthemethod.

Note

NotethatapropertydescriptorisanobjectthatcanbeobtainedbyinvokingtheObject.getOwnPropertyDescriptor()method.

Let'sdeclareamethoddecoratornamedlogMethodwithoutanybehaviorfornow:

functionlogMethod(target:any,key:string,descriptor:any){

//...

}

WecanapplythedecoratortooneofthemethodsinthePersonclass:

//...

@logMethod

publicsaySomething(something:string):string{

returnthis.name+""+this.surname+"says:"+something;

}

//...

Themethoddecoratorisinvokedusingthefollowingarguments:

TheprototypeoftheclassthatcontainsthemethodbeingdecoratedisPerson.prototypeThenameofthemethodbeingdecoratedissaySomethingThepropertydescriptorofthemethodbeingdecoratedisObject.getOwnPropertyDescriptor(Person.prototype,saySomething)

Nowthatweknowthevalueofthedecoratorparameters,wecanproceedtoimplementit:

functionlogMethod(target:any,key:string,descriptor:any){

Page 334: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

//saveareferencetotheoriginalmethod

varoriginalMethod=descriptor.value;

//editingthedescriptor/valueparameter

descriptor.value=function(...args:any[]){

//convertmethodargumentstostring

vara=args.map(a=>JSON.stringify(a)).join();

//invokemethodandgetitsreturnvalue

varresult=originalMethod.apply(this,args);

//convertresulttostring

varr=JSON.stringify(result);

//displayinconsolethefunctioncalldetails

console.log(`Call:${key}(${a})=>${r}`);

//returntheresultofinvokingthemethod

returnresult;

}

//returnediteddescriptor

returndescriptor;

}

Justlikewedidwhenweimplementedtheclassdecorator,westartbycreatingacopyoftheelementbeingdecorated.Insteadofaccessingthemethodviatheclassprototype(target["key"]),wewillaccessitviathepropertydescriptor(descriptor.value).

Wethencreateanewfunctionthatwillreplacethemethodbeingdecorated.Thenewfunctioninvokestheoriginalmethodbutalsocontainssomeadditionallogicusedtologintheconsole,themethodname,andthevalueofitsargumentseverytimeitisinvoked.

Afterapplyingthedecoratortothemethod,themethodnameandargumentswillbeloggedintheconsolewhenitisinvoked:

varme=newPerson("Remo","Jansen");

me.saySomething("hello!");

//Call:saySomething("hello!")=>"RemoJansensays:hello!"

Page 335: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThepropertydecoratorsTheofficialTypeScriptdecoratorproposaldefinesapropertydecoratorasfollows:

Apropertydecoratorfunctionisafunctionthatacceptstwoarguments:Theobjectthatownsthepropertyandthekeyfortheproperty(astringorasymbol).Apropertydecoratordoesnotreturn.

--"DecoratorsProposal–TypeScript"byRonBuckton

Apropertydecoratorisreallysimilartoamethoddecorator.Themaindifferencesarethatapropertydecoratordoesn'treturnavalueandthatthethirdparameter(thepropertydescriptor)isnotpassedtothepropertydecorator.

Let'screateapropertydecoratornamedlogPropertytoseehowitworks:

functionlogProperty(target:any,key:string){

//...

}

WecanuseitinoneofthePersonclass'spropertiesasfollows:

classPerson{

@logProperty

publicname:string;

//...

Aswehavebeendoingsofar,wearegoingtoimplementadecoratorthatwilloverridethedecoratedpropertywithanewpropertythatwillbehaveexactlyastheoriginalone,butwillperformanadditionaltask—loggingthepropertyvalueintheconsolewheneveritchanges:

functionlogProperty(target:any,key:string){

//propertyvalue

var_val=this[key];

//propertygetter

vargetter=function(){

console.log(`Get:${key}=>${_val}`);

return_val;

};

//propertysetter

varsetter=function(newVal){

console.log(`Set:${key}=>${newVal}`);

_val=newVal;

};

//Deleteproperty.Thedeleteoperatorthrows

//instrictmodeifthepropertyisanown

//non-configurablepropertyandreturns

Page 336: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

//falseinnon-strictmode.

if(deletethis[key]){

Object.defineProperty(target,key,{

get:getter,

set:setter,

enumerable:true,

configurable:true

});

}

}

Intheprecedingdecorator,wecreatedacopyoftheoriginalpropertyvalueanddeclaredtwofunctions:getter(invokedwhenwechangethevalueoftheproperty)andsetter(invokedwhenwereadthevalueoftheproperty)respectively.

Inthepreviousdecorator,thereturnvaluewasusedtooverridetheelementbeingdecorated.Becausethepropertydecoratordoesn'treturnavalue,wecan'toverridethepropertybeingdecoratedbutwecanreplaceit.WehavemanuallydeletedtheoriginalpropertyandcreatedanewpropertyusingtheObject.definePropertyfunctionandthepreviouslydeclaredgetterandsetterfunctions.

Afterapplyingthedecoratortothenameproperty,wewillbeabletoobserveanychangestoitsvalueintheconsole:

varme=newPerson("Remo","Jansen");

//Set:name=>Remo

me.name="RemoH.";

//Set:name=>RemoH.

varn=me.name;

//Get:nameRemoH.

Page 337: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheparameterdecoratorsTheofficialdecoratorproposaldefinesaparameterdecoratorasfollows:

Aparameterdecoratorfunctionisafunctionthatacceptsthreearguments:Theobjectthatownsthemethodthatcontainsthedecoratedparameter,thepropertykeyoftheproperty(orundefinedforaparameteroftheconstructor),andtheordinalindexoftheparameter.Thereturnvalueofthisdecoratorisignored.

DecoratorsProposal–TypeScript"byRonBuckton

Let'screateaparameterdecoratornamedaddMetadatatoseehowitworks:

functionaddMetadata(target:any,key:string,index:number){

//...

}

Wecanapplythepropertydecoratortoaparameterasfollows:

publicsaySomething(@addMetadatasomething:string):string{

returnthis.name+""+this.surname+"says:"+something;

}

Theparameterdecoratordoesn'treturn,whichmeansthatwewillnotbeabletooverridethemethodthatcontainstheparameterbeingdecorated.

Wecanuseparameterdecoratorstoaddsomemetadatatotheprototype(target)class.Inthefollowingimplementation,wewilladdanarraynamedlog_${key}_parametersasaclasspropertywherekeyisthenameofthemethodthatcontainstheparameterbeingdecorated:

functionaddMetadata(target:any,key:string,index:number){

varmetadataKey=`_log_${key}_parameters`;

if(Array.isArray(target[metadataKey])){

target[metadataKey].push(index);

}

else{

target[metadataKey]=[index];

}

}

Toallowmorethanoneparametertobedecorated,wecheckwhetherthenewfieldisanarray.Ifthenewfieldisnotanarray,wecreateandinitializethenewfieldtobeanewarraycontainingtheindexoftheparameterbeingdecorated.Ifthenewfieldisanarray,theindexoftheparameterbeingdecoratedisaddedtothearray.

Aparameterdecoratorisnotreallyusefulonitsown;itneedstobecombinedwithamethoddecorator,sotheparameterdecoratoraddsthemetadataandthemethoddecoratorreadsit:

@readMetadata

publicsaySomething(@addMetadatasomething:string):string{

Page 338: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnthis.name+""+this.surname+"says:"+something;

}

Thefollowingmethoddecoratorworkslikethemethoddecoratorthatweimplementedpreviouslyinthischapter,butitwillreadthemetadataaddedbytheparameterdecoratorandinsteadofdisplayingalltheargumentspassedtothemethodintheconsolewhenitisinvoked,itwillonlylogtheonesthathavebeendecorated:

functionreadMetadata(target:any,key:string,descriptor:any){

varoriginalMethod=descriptor.value;

descriptor.value=function(...args:any[]){

varmetadataKey=`_log_${key}_parameters`;

varindices=target[metadataKey];

if(Array.isArray(indices)){

for(vari=0;i<args.length;i++){

if(indices.indexOf(i)!==-1){

vararg=args[i];

varargStr=JSON.stringify(arg)||arg.toString();

console.log(`${key}arg[${i}]:${argStr}`);

}

}

varresult=originalMethod.apply(this,args);

returnresult;

}

}

returndescriptor;

}

IfweapplythesaySomethingmethod:

varperson=newPerson("Remo","Jansen");

person.saySomething("hello!");

ThereadMetadatadecoratorwilldisplaythevalueoftheparametersthatwereaddedtothemetadata(theclasspropertynamed_log_saySomething_parameters)intheconsolebytheaddMetadatadecorator:

saySomethingarg[0]:"hello!"

Note

Notethat,inthepreviousexample,weusedaclasspropertytostoresomemetadata.Laterinthischapter,youwilllearnhowtousethereflectionmetadataAPI;thisAPIhasbeendesignedspecificallytogenerateandreadmetadataanditis,therefore,recommendedtouseitwhenweneedtoworkwithdecoratorsandmetadata.

Page 339: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThedecoratorfactoryTheofficialdecoratorproposaldefinesadecoratorfactoryasfollows:

Adecoratorfactoryisafunctionthatcanacceptanynumberofarguments,andmustreturnoneoftheabovetypesofdecoratorfunction.

DecoratorsProposal–TypeScript"byRonBuckton

Youlearnedtoimplementclass,property,method,andparameterdecorators.Inthemajorityofcases,wewillconsumedecorators,notimplementthem.Forexample,inAngular2.0,wewillusean@viewdecoratortodeclarethataclasswillbehaveasaView,butwewillnotimplementthe@viewdecoratorourselves.

Wecanusethedecoratorfactorytomakedecoratorseasiertoconsume.Let'sconsiderthefollowingcodesnippet:

@logClass

classPerson{

@logProperty

publicname:string;

publicsurname:string;

constructor(name:string,surname:string){

this.name=name;

this.surname=surname;

}

@logMethod

publicsaySomething(@logParametersomething:string):string{

returnthis.name+""+this.surname+"says:"+something;

}

}

Theproblemwiththeprecedingcodeisthatwe,asdevelopers,needtoknowthatthelogMethoddecoratorcanonlybeappliedtoamethod.Thismightseemtrivialbecausethedecoratornamingusedabovemakesiteasierforus.

Abettersolutionistoenabledeveloperstousean@logdecoratorwithouthavingtoworryaboutusingtherightkindofdecorator:

@log

classPerson{

@log

publicname:string;

publicsurname:string;

constructor(name:string,surname:string){

this.name=name;

Page 340: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.surname=surname;

}

@log

publicsaySomething(@logsomething:string):string{

returnthis.name+""+this.surname+"says:"+something;

}

}

Wecanachievethisbycreatingadecoratorfactory.Adecoratorfactoryisafunctionthatisabletoidentifywhatkindofdecoratorisrequiredandreturnit:

functionlog(...args:any[]){

switch(args.length){

case1:

returnlogClass.apply(this,args);

case2:

//breakinsteadofreturnasproperty

//decoratorsdon'thaveareturn

logProperty.apply(this,args);

break;

case3:

if(typeofargs[2]==="number"){

logParameter.apply(this,args);

}

returnlogMethod.apply(this,args);

default:

thrownewError("Decoratorsarenotvalidhere!");

}

}

Aswecanobserveintheprecedingcodesnippet,thedecoratorfactoryusesthenumberandtypeofargumentspassedtothedecoratortoidentifytherequiredkindofdecorator.

Page 341: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DecoratorswithargumentsWecanuseaspecialkindofdecoratorfactorytoallowdeveloperstoconfigurethebehaviorofadecorator.Forexample,wecouldpassastringtoaclassdecoratorasfollows:

@logClass("option")

classPerson{

//...

Inordertobeabletopasssomeparameterstoadecorator,weneedtowrapthedecoratorwithafunction.Thewrapperfunctiontakestheparametersofourchoiceandreturnsadecorator:

functionlogClass(option:string){

returnfunction(target:any){

//classdecoratorlogicgoeshere

//wehaveaccesstothedecoratorparameters

console.log(target,option);

}

}

Thiscanbeappliedtoallthekindsofdecoratorthatyoulearnedaboutinthischapter.

Page 342: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThereflectionmetadataAPIYoulearnedthatdecoratorscanbeusedtomodifyandextendthebehaviorofaclass'smethodsorproperties.Youalsolearnedthatwecanusedecoratorstoaddmetadatatotheclassbeingdecorated.

Forlessexperienceddevelopers,thepossibilityofaddingmetadatatoaclassmightnotseemreallyusefulorexcitingbutitisoneofthegreatestthingsthathashappenedtoJavaScriptinthepastfewyears.

Aswealreadyknow,TypeScriptonlyusestypesatdesigntime.However,somefeaturessuchasdependencyinjection,runtimetypeassertions,reflection,andtestingarenotpossiblewithoutthetypeinformationbeingavailableatruntime.Thisisnotaproblemanymorebecausewecanusedecoratorstogeneratemetadataandthatmetadatacancontaintypeinformation.Themetadatacanthenbeprocessedatruntime.

WhentheTypeScriptteamstartedtothinkaboutthebestpossiblewaytoallowdeveloperstogeneratetypeinformationmetadata,theyreservedafewspecialdecoratornamesforthispurposes.

Theideawasthat,whenanelementwasdecoratedusingthesereserveddecorators,thecompilerwouldautomaticallyaddthetypeinformationtotheelementbeingdecorated.Thereserveddecoratorswerethefollowing:

TypeScriptcompilerwillhonorspecialdecoratornamesandwillflowadditionalinformationintothedecoratorfactoryparametersannotatedbythesedecorators.

@type–Theserializedformofthetypeofthedecoratortarget

@returnType–Theserializedformofthereturntypeofthedecoratortargetifitisafunctiontype,undefinedotherwise

@parameterTypes–Alistofserializedtypesofthedecoratortarget'sargumentsifitisafunctiontype,undefinedotherwise

@name–Thenameofthedecoratortarget

--"Decoratorsbrainstorming"byJonathanTurner

Shortlyafter,theTypeScriptteamdecidedtousethereflectionmetadataAPI(oneoftheproposedES7features)insteadofthereserveddecorators.

Theideaisalmostidenticalbutinsteadofusingthereserveddecoratornames,wewillusesomereservedmetadatakeystoretrievethemetadatausingthereflectionmetadataAPI.TheTypeScriptdocumentationdefinesthreereservedmetadatakeys:

Page 343: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Typemetadatausesthemetadatakey"design:type".

Parametertypemetadatausesthemetadatakey"design:paramtypes".

Returntypemetadatausesthemetadatakey"design:returntype".

--Issue#2577-TypeScriptOfficialRepositoryatGitHub.com

Let'sseehowwecanusethereflectionmetadataAPI.Weneedtostartbyreferencingandimportingtherequiredreflect-metadatanpmpackage:

///<referencepath="./node_modules/reflect-metadata/reflect-metadata.d.ts"/>

import'reflect-metadata';

Wecanthencreateaclassfortestingpurposes.Wearegoingtogetthetypeofoneoftheclasspropertiesatruntime.WearegoingtodecoratetheclassusingapropertydecoratornamedlogType:

classDemo{

@logType

publicattr1:string;

}

Insteadofusingareserveddecorator,@type,weneedtoinvoketheReflect.getMetadata()methodandpassthedesign:typekey.Thetypesarereturnedasfunctions,forexample,forthetypestring,thefunctionString(){}functionisreturned.Wecanusethefunction.namepropertytogetthetypeasastring:

functionlogType(target:any,key:string){

vart=Reflect.getMetadata('design:type',target,key);

console.log(`${key}type:${t.name}`);

}

IfwecompiletheprecedingcodeandruntheresultingJavaScriptcodeinawebbrowser,wewillbeabletoseethetypeoftheattr1propertyintheconsole:

'attr1type:String'

Note

Rememberthat,inordertorunthisexample,thereflect-medatadalibrarymustbeimported.

Wecanapplytheotherreservedmetadatakeysinasimilarmanner.Let'screateamethodwithmanyparameterstousethedesign:paramtypesreservedmetadatakeytoretrievethetypesoftheparameters

classDemo{

@logParamTypes

publicdoSomething(

param1:string,

Page 344: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

param2:number,

param3:Foo,

param4:{test:string},

param5:IFoo,

param6:Function,

param7:(a:number)=>void

):number{

return1;

}

}

Thistime,wewillusethedesign:paramtypesreservedmetadatakey,andbecausewearequeryingthetypesofmultipleparameters,thetypeswillbereturnedasanarraybytheReflect.getMetadata()function:

functionlogParamTypes(target:any,key:string){

vartypes=Reflect.getMetadata('design:paramtypes',target,key);

vars=types.map(a=>a.name).join();

console.log(`${key}paramtypes:${s}`);

}

Ifwecompileandruntheprecedingcodeinawebbrowser,wewillbeabletoseethetypesoftheparametersintheconsole:

'doSomethingparamtypes:String,Number,Foo,Object,Object,Function,

Function'

Thetypesareserializedandfollowsomerules.WecanseethatfunctionsareserializedasFunction,objectsliterals({test:string})andinterfacesareserializedasObject,andsoon:

Type Serialized

void undefined

string String

number Number

boolean Boolean

symbol Symbol

any Object

Page 345: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

enum Number

ClassC{} C

Objectliteral{} Object

interface Object

Note

Notethatsomedevelopershaverequiredthepossibilityofaccessingthetypeofinterfacesandtheinheritancetreeofaclassviametadata.Thisfeatureisknownascomplextypeserializationandisnotavailableatthetimeofwritingthisbook,buttheTypeScriptteamhasalreadystartedtoworkonit.

Toconclude,wearegoingtocreateamethodwithareturntypeandusethedesign:returntypereservedmetadatakeytoretrievethetypesofthereturntype:

classDemo{

@logReturntype

publicdoSomething2():string{

return"test";

}

}

Justlikeinthetwopreviousdecorators,weneedtoinvoketheReflect.getMetadata()function,passingthedesign:returntypereservedmetadatakey:

functionlogReturntype(target,key){

varreturnType=Reflect.getMetadata('design:returntype',target,key);

console.log(`${key}returntype:${returnType.name}`);

}

Ifwecompileandruntheprecedingcodeinawebbrowser,wewillbeabletoseethetypesofthereturntypeintheconsole:

'doSomething2returntype:String'

Page 346: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,youlearnedhowtoconsumeandimplementthefouravailabletypesofdecorators(class,method,property,andparameter)andhowtocreateadecoratorfactorytoabstractdevelopersfromthedecoratortypeswhentheyareconsumed.

YoualsolearnedhowtousethereflectionmetadataAPItoaccesstypeinformationatruntime.

Inthenextchapter,youwilllearnaboutthearchitectureofaTypeScriptapplication.Youwillalsolearnabouthowtoworkwithsomedesignpatternsandhowtocreateasingle-pagewebapplication.

Page 347: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter9.ApplicationArchitectureInpreviouschapters,wehavecoveredseveralaspectsofTypeScript,andweshouldnowfeelconfidentenoughtocreateasmallapplication.

Asweknow,TypeScriptwascreatedbyMicrosofttofacilitatethecreationoflarge-scaleJavaScriptapplications.SomeTypeScriptfeaturessuchasmodulesorclassescanfacilitatetheprocessofcreatinglargeapplications,butitisnotenough.Weneedgoodapplicationarchitectureifwewanttosucceedinthelongterm.

Thischapterisdividedintotwomainparts.Inthefirstpart,wearegoingtolookatthesingle-pageapplication(SPA)architectureandsomedesignpatternsthatwillhelpuscreatescalableandmaintainableapplications.Thissectioncoversthefollowingtopics:

Thesingle-pagewebapplicationarchitectureTheMV*architectureModelsandcollectionsItemviewsandcollectionviewsControllersEventsRouterandhashnavigationMediatorClient-siderenderingandvirtualDOMDatabindinganddataflowThewebcomponentandshadowDOMChoosinganMV*framework

Inthesecondpartofthischapter,wearegoingtoputintopracticemanyofthetheoreticalconceptsexploredinthefirstpartofthischapter.Wearegoingtodevelopasingle-pagewebapplicationframework,fromscratch,whichwillbeusedtocreateanapplicationinChapter10,PuttingEverythingTogether.

Page 348: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thesingle-pageapplicationarchitectureWearegoingtostartbyexploringwhatsingle-pageapplications(SPAs)areandhowtheywork.NumerousSPAframeworksareavailablethatcanhelpusdevelopapplicationswithagoodarchitecture.

Wecouldjumpdirectlyintotheuseofoneoftheseframeworks,butitisalwaysagoodthingtounderstandhowathird-partysoftwarecomponentworksbeforeweuseit.Forthisreason,wearegoingusethefirstpartofthischaptertostudytheinternalarchitectureofanSPA.Let'sstartbyunderstandingwhatanSPAis.

AnSPAisawebapplicationinwhichalltheresources(HTML,CSS,JavaScript,andsoon)areeitherloadedinonesinglerequest,orloadeddynamicallywithoutfullyreloadingthepage.Weusethetermsingle-pagetorefertothiskindofapplicationbecausethewebpageisneverfullyreloadedaftertheinitialpageload.

Inthepast,theWebwasjustacollectionofstaticHTMLfilesandhyperlinks;everytimeweclickedonahyperlink,anewpagewasloaded.Thisaffectedwebapplicationperformancenegativelybecausemanyofthecontentsofthepage(forexample,pageheaders,pagefooters,sidemenus,scripts)wereloadedagainwitheachnewpage.

WhenAJAXsupportarrivedforwebbrowsers,developersstartedtoloadsomeofthepagecontentviaAJAXrequeststoavoidunnecessarypagereloadsandprovidebetteruserexperience.AJAXapplicationsandSPAsworkinaverysimilarway.ThesignificantdifferenceisthatAJAXapplicationsloadsectionsofthewebapplicationasHTML.ThesesectionsarereadytobeappendedtotheDOMassoonastheyfinishloading.Ontheotherhand,SPAsavoidloadingtheHTML;instead,theyloaddataandclient-sidetemplates.ThetemplatesanddataareprocessedandtransformedintoHTMLinthewebbrowserinaprocessknownasclient-siderendering.ThedataisusuallyinXMLorJSONformat,andtherearemanyavailableclient-sidetemplatelanguages.

Let'scomparebothapproachesindetail.Forexample,toshowalistofclientsandordersinanHTMLtableusingtheAJAXapplicationapproach,wecouldloadtheinitialpagecontainingthelistofclientsinHTMLformat,readytobedisplayed.Inthetable,wewouldusearowforeachclient:

<tr>

<td>ClientName1</td>

<td>

<ahref="javascript:void(0);"class="orders_link"data-client-id="1">

ViewOrders

</a>

</td>

<!--morecolumns...-->

</tr>

Note

Page 349: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Youdon'tneedtocreatenewfoldersorfilesfornow.Thisisatheoreticalexampleandisnotmeantobeimplementedorexecuted.

WewouldalsoneedsomeJavaScriptcodetoloadtheclientordersviaAJAXwhenauserclicksontheViewOrderslink:

$(document).ready(){

//loadanddisplayclientorders

functiondisplayOrders(userId){

$.ajax({

method:"GET",

url:`/client/orders.aspx?id=${userId}`,

dataType:"html",

success:function(html){

$("#page_container").html(html);

},

error:function(e){

varmsg="<h1>Sorry,therehasbeenanerror!</h1>";

$("#page_container").html(msg);

}

});

}

//setclickevent

$('.orders_link').on('click',function(e){

varuserId=$(e.currentTarget).data("client-id");

displayOrders(userId);

});

}

Note

RefertotheHandlebars.js(http://handlebarsjs.com/)andJQueryAJAX(http://api.jquery.com/jquery.ajax/)documentationifyouneedadditionalhelptounderstandtheprecedingexample.

Theprecedingcodesnippetwaitsforthepagetofinishloadingbyusingadocument-readyeventhandler.Thenitaddsaneventhandlerforclickeventsonelementswithaclassattributeequaltoorders_link.

TheeventhandlertakestheuserIDfromthedata-client-idattributeandpassesittothedisplayOrdersfunction.ThedisplayOrdersfunctionusesanAJAXrequesttoloadthelistoforders.ThelistofordersisinHTMLformatandcanbeinsertedintotheDOMwithoutchangingitsformat.

InanSPA,theprocessisverysimilar.TheinitialHTMLpage(containingthelistofclients)isloadedjustlikeintheAJAXapplication.InSPAs,thenavigationtoanewpageisalsomanagedbyJavaScriptevents,butitisusuallymanagedbyacomponentknownasRouter.

Let'signorenavigationinSPAsfornowandfocusontheloadingandrendering.InanSPA,

Page 350: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

wewillnotloadalistofordersinHTMLformat;wewillloaditusingtheXMLorJSONformats.IfweuseJSON,theresponsemaylooklikethefollowingone:

{

"orders":[

{

"order_id":32423234,

"currency":"EUR",

"date":"13-02-2015,

"items":[

{"product_id":13223523,"price":150.00,"quantity":2}

{"product_id":62352355,"price":50.00,"quantity":1}

]

},

{

"order_id":32423786,

"currency":"EUR",

"date":"13-02-2015,

"items":[

{"product_id":13228898,"price":60.00,"quantity":1}

]

}

]

}

WecanuseanAJAXrequestalmostidenticaltotheonethatweusedtoloadHTMLintheAJAXapplication:

functiongetOrdersData(userId:number,cb){

$.ajax({

method:"GET",

url:`/api/orders/${userId}`,

dataType:"json",

success:function(json){

cb(json);

},

error:function(e){

varmsg="<h1>Sorry,therehasbeenanerror!</h1>";

$("#page_container").html(msg);

}

});

}

Beforewecanshowthelistofordersinthewebbrowser,weneedtotransformitintoHTML.TotransformtheJSONintoHTML,wecanuseatemplatesystem.Therearemanytemplatesystems,butwearegoinguseaHandlebarstemplateforthisexample.Let'stakealookatthesyntaxofoneofthesetemplates:

{{#eachorders}}

<tr>

<td>{{order_id}}</td>

<td>{{date}}</td>

<td>

<ul>

Page 351: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

{{#eachitems}}<li>{{product_id}}x{{quantity}}</li>{{/each}}

</ul>

</td>

</tr>

{{/each}}

TheelementsoftheHandlebarstemplatelanguagearewrappedwithdoublebrackets({{and}}).Theprecedingtemplatestartswithaneachflowcontrolstatement.Theeachstatementisusedtorepeatsomeinstructionsforeachoftheelementsinanarray.IfwetakealookattheJSONresponse,wewillbeabletoseethattheorderselementisanarray.Thetemplatewillrepeattheoperationsbetween{{#eachorders}}and{{/each}}onceforeachobjectintheordersarray.

EachrepetitioncreatesanewHTMLtablerow.TodisplaythevalueofoneoftheJSONfieldsintheHTMLoutput,wejustneedtorefertothefieldwrappedarounddoublebrackets.Forexample,whenwerenderthecellcontainingtheorderID,weuse{{order_id}}.

Note

WhenreferringtoaJSONfieldinatemplate,thefieldmustbeinthecurrentscope.Thescopecanbeexplicitlyaccessedusingthethiskeyword,forexample,{{this.order_id}}isequalto{{order_id}}.Thescopeinatemplatechangeswhenweusesomeoftheavailableflowcontrolsentences.Forexample,the{{#eachorders}}statementassignsthecurrentiteminthearraytothethiskeyword.

InordertouseaHandlebarstemplate,weneedtoloadandcompileit.WecanloadthetemplateusingaregularAJAXrequest:

functiongetOrdersTemplate(cb){

$.ajax({

method:"GET",

url:"/client/orders.hbs",

dataType:"text",

success:function(templateSource){

vartemplate=Handlebars.compile(source);

cb(template);

},

error:function(e){

varmsg="<h1>Sorry,therehasbeenanerror!</h1>";

$("#page_container").html(msg);

}

});

}

Intheprecedingexample,wehaveloadedatemplateusinganAJAXrequestandcompileditusingtheHandlebarscompilemethod.

Note

Inarealproductionwebsite,templatesareusuallyprecompiledbythecontinuousintegration

Page 352: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

build.Thetemplatesarethenreadytobeusedwhentheyfinishloading.Precompilingthetemplatescanhelptoimprovetheapplication'sperformance.

Wehavecreatedtwofunctions:onetoloadthetemplateandcompileitandtheothertoloadtheJSONdata.ThelaststepistocreateafunctionthatputstogetherthetemplateandtheJSONdatatogeneratetheHTMLtable,whichcontainsthelistofclientorders:

functiondisplayOrders(userId){

getOrdersData(userId,function(data){

getOrdersTemplate(data,function(template){

varhtml=template(json);

$("#page_container").html(html);

});

});

}

ItmayseemlikeSPAsrequiremuchmoreworkandthattheycouldcausepoorperformancecomparedwithAJAXapplicationsbecausetherearebothmoreoperationsandrequeststobeperformedinthewebbrowser.However,thatisfarfromthereality.TounderstandthebenefitsofSPAs,weneedtounderstandwhytheywerecreatedinthefirstplace.

ThecreationofSPAswashighlyinfluencedbytwoevents:thefirstoneistheexponentialincreaseofthepopularityofmobiledevicesandtabletswithInternetaccessandpowerfulhardware.ThesecondeventistheimprovementofJavaScriptperformancethattookplaceduringthesameperiodoftime.

Asmobiledevicesgainedpopularity,companieswereforcedtodevelopamobileversionofthesameclientapplication.CompaniesstarteddevelopingwebservicestogenerateJSONandXML(insteadofHTMLpages)thatcouldbeconsumedbyeachoftheseclientapplications.Thesewebservicescouldbeusedbyallapplications,thusallowingcompaniestoreducecosts.

TheproblemwasthattheexistingAJAXapplicationscouldnottakeadvantageofthewebserviceswithoutaclient-siderenderingsystem.TemplatesystemssuchasMustache(thepredecessorofHandlebars)werereleasedforthefirsttimetosolvethisproblem.

OneofthemainadvantagesofSPAsisthatweneedanHTTPAPI.AnHTTPAPIhasmanyadvantagesoveranapplicationthatrendersHTMLpagesintheserverside.Forexample,wecanwriteunittestsforawebservicewitheasebecauseassertingdataismucheasierthanassertingsomeuserinteractionfunctionality.HTTPAPIscanbeusedbymanyclientapplications,whichcanreducecostsandopennewlinesofbusiness,suchassellingtheHTTPAPIasaproduct.

AnotherimportantadvantageofSPAsisthatbecausealotoftheworkisperformedinthewebbrowser,theserverperformsfewertasksandisabletohandleahighernumberofrequests.Client-sideperformanceisnotnegativelyaffectedbecausepersonalcomputersandmobiledeviceshavebecomereallypowerfulandJavaScriptperformancehasimprovedsignificantly

Page 353: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

overthelastfewyears.

NetworkperformanceinSPAscanbebothbetterandworsewhencomparedtonetworkperformanceinAJAXapplications.TheresponseformattedintheHTMLformatcansometimesbeheavierthanthedatainJSONorXMLformats.

ThepricetopaywhenusingJSONorXMListhatbutwewillperformanextrawebrequesttofetchthetemplate.Wecansolvetheseproblemsbypre-compilingthetemplates,implementingcachingmechanismsandjoiningsmalltemplatefilesintolargertemplatefilestoreducethenumberofrequests.

Page 354: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheMV*architectureAswehaveseen,manytasksthatweretraditionallyperformedontheserversideareperformedontheclientsideinSPAs.ThishascausedanincreaseinthesizeofJavaScriptapplicationsandtheneedforabettercodeorganization.

Asaresult,developershavestartedusinginthefrontendsomeofthedesignpatternsthathavebeenusedwithsuccessinthebackendoverthelastdecade.Amongthose,wecanhighlighttheModel-View-Controller(MVC)designpatternandsomeofitsderivativeversions,suchasModel-View-ViewModel(MVVM)andModel-View-Presenter(MVP).

DevelopersaroundtheworldstartedtosharesomeSPAframeworksthatsomehowtrytoimplementtheMVCdesignpatternbutdonotnecessarilyfollowtheMVCpatternstrictly.ThemajorityoftheseframeworksimplementModelsandViews,butsincenotallofthemimplementControllers,wereferstothisfamilyofframeworksasMV*.

Note

WewillcoverconceptssuchasMVC,Models,andViewslaterinthischapter.

Wewillnowlookatotherarchitectureprinciples,designpatterns,andcomponentscommonlypresentinMV*frameworks.

Page 355: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CommoncomponentsandfeaturesintheMV*frameworksWehaveseenthatsingle-pagewebapplicationsareusuallydevelopedusingafamilyofframeworksknownasMV*,andwehavecoveredthebasicsofsomecommonSPAarchitectureprinciples.

Let'sdelvefurtherintosomecomponentsandfeaturesthatarecommonlyfoundinMV*frameworks.

Note

Inthissection,wewillusesomesmallcodesnippetsfromsomeofthemostpopularMV*frameworks.Wearenotattemptingtolearnhowtouseeachoftheseframeworks,andnopreviousexperiencewithanMV*frameworkisrequired.

OurgoalshouldbetounderstandthecommoncomponentsandfeaturesofanMV*frameworkandnotfocusonaparticularframework.

Page 356: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ModelsAmodelisacomponentusedtostoredata.ThedataisretrievedfromanHTTPAPIanddisplayedintheview.Someframeworksincludeamodelentitythatwe,asdevelopers,mustextend.Forexample,inBackbone.js(apopularMV*framework),amodelmustextendtheBackbone.Modelclass:

classTaskModelextendsBackbone.Model{

publiccreated:number;

publiccompleted:boolean;

publictitle:string;

constructor(){

super();

}

}

Amodelinheritssomemethodsthatcanhelpusinteractwiththewebservices.Forexample,inthecaseofaBackbone.jsmodel,wecanuseamethodnamedfetchtosetthevaluesofamodelusingthedatareturnedbyawebservice.Insomeframeworks,modelsincludelogictoretrievedatafromanHTTPAPI,whileothersincludeanindependentcomponentresponsibleforthecommunicationwithanHTTPAPI.

Inotherframeworks,modelsareplainentities,anditisnotnecessarytoextendorinstantiateoneoftheframework'sclasses:

classTaskModel{

publiccreated:number;

publiccompleted:boolean;

publictitle:string;

}

Page 357: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CollectionsCollectionsareusedtorepresentalistofmodels.Intheprevioussection,wesawanexampleofamodelnamedTaskModel.Whilethismodelcouldbeusedtorepresentasingletaskinalistofthingstodo,acollectioncouldbeusedtorepresentthelistoftasks.

InthemajorityofMV*frameworksthatsupportcollections,weneedtospecifythemodeloftheitemsofacollectionwhenthecollectionisdeclared.Forexample,inthecaseofBackbone.js,theTaskcollectioncouldlooklikethefollowing:

classTaskCollectionextendsBackbone.Collection<TaskModel>{

publicmodel:TaskModel;

constructor(){

this.model=TodoModel;

super();

}

}

Justlikeinthecaseofmodels,someframeworks'collectionsareplainarrays,andwewillnotneedtoextendorinstantiateoneoftheframework'sclasses.Collectionscanalsoinheritsomemethodstofacilitateinteractionwithwebservices.

Page 358: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ItemviewsThemajorityofframeworksfeatureanitemview(orjustview)component.ViewsareresponsibleforrenderingthedatastoredinthemodelsasHTML.Viewsusuallyrequireamodel,atemplate,andacontainertobepassedasaconstructorargument,property,orsetting.

ThemodelandthetemplateareusedtogeneratetheHTML,aswediscoveredearlieroninthischapterThecontainerisusuallytheselectorofoneoftheDOMelementsinthepage;theselectedDOMelementisthenusedasacontainerfortheHTML,whichisinsertedorappendedtoit

Forexample,inMarionette.js(apopularMV*frameworkbasedonBackbone.js),aviewisdeclaredasfollows:

classNavBarItemViewextendsMarionette.ItemView{

constructor(options:any={}){

options.template="#navBarItemViewTemplate";

super(options);

}

}

Page 359: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CollectionviewsAcollectionviewisaspecialtypeofview.Therelationshipbetweencollectionviewsandviewsissomehowcomparablewiththerelationshipbetweencollectionsandmodels.Collectionviewsusuallyrequireacollection,anitemview,andacontainertobepassedasaconstructorargument,property,orsetting.

Acollectionloopsthroughthemodelsinthespecifiedcollection,renderseachofthemusingaspecifieditemview,andthenappendstheresultsofthecontainer.

Note

Inthemajorityofframeworks,whenacollectionviewisrendered,anitemviewisrenderedforeachiteminthecollection;thiscansometimescreateaperformancebottleneck.

Analternativesolutionistouseanitemviewandamodelinwhichoneofitsattributesisanarray.Wecanthenusethe{{#each}}statementintheviewtemplatetorenderacollectioninonesingleoperation,asopposedtooneoperationforeachiteminthecollection.

ThefollowingcodesnippetisanexampleofacollectionviewinMarionette.js:

classSampleCollectionViewextendsMarionette.CollectionView<SampleModel>{

constructor(options:any={}){

super(options);

}

}

varview=newSampleCollectionView({

collection:collection,

el:$("#divOutput"),

childView:SampleView

});

Page 360: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ControllersSomeframeworksfeatureControllers.Controllersareusuallyinchargeofhandlingthelifecycleofspecificmodelsandtheirassociatedviews.Theyareresponsibleforinstantiatingconnectionmodelsandcollectionswiththeirrespectiveviewsandcollectionviewsaswellasdisposingthembeforehandlingthecontrolovertoanothercontroller.

InteractioninMVCapplicationsisorganizedaroundcontrollersandactions.Controllerscanincludeasmanyactionmethodsasneeded,andanactiontypicallyhasone-to-onemappingwithuserinteractions.

WearegoingtotakealookatasmallcodesnippetthatusesanMV*frameworkknownasChaplin.JustlikeMarionette.js,ChaplinisaframeworkbasedonBackbone.js.ThefollowingcodesnippetdefinesaclassthatinheritsfromthebaseControllerclass,whichisdefinedbyChaplin:

classLikesControllerextendsChaplin.Controller{

publicbeforeAction(){

this.redirectUnlessLoggedIn();

}

publicindex(params){

this.collection=newLikes();

this.view=newLikesView({collection:this.collection});

}

publicshow(params){

this.model=newLike({id:params.id});

this.view=newFullLikeView({model:this.model});

}

}

Intheprecedingcodesnippet,wecanseethatthecontrollerisnamedLikesController,andithastwoactionsnamedindexandshowrespectively.WecanalsoobserveamethodnamedbeforeActionthatisexecutedbyChaplinbeforeanactionisinvoked.

Page 361: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

EventsAneventisanactionoroccurrencedetectedbytheprogramthatmaybehandledbytheprogram.MV*frameworksusuallydistinguishtwokindsofevents:

Userevents:Applicationsallowuserstointeractwithitbytriggeringandhandlinguserevents,suchasclickingonabutton,scrolling,orsubmittingaform.Usereventsareusuallyhandledinaview.Applicationevents:Theapplicationcanalsotriggerandhandleevents.Forexample,someframeworkstriggeranonRendereventwhenaviewhasbeenrenderedoranonBeforeRoutingeventwhenacontrolleractionisabouttobeinvoked.

ApplicationeventsareagoodwaytoadheretotheOpen/CloseelementoftheSOLIDprinciple.Wecanuseeventstoallowdeveloperstoextendaframework(byaddingeventhandlers)withouthavingtomodifytheframeworkitself.

Applicationeventscanalsobeusedtoavoiddirectcommunicationbetweentwocomponents.WewillcovermoreaboutthemlaterinthischapterwhenwefocusonacomponentknownasMediator.

Page 362: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Routerandhash(#)navigationTherouterisresponsibleforobservingURLchangesandpassingtheexecutionflowtoacontroller'sactionthatmatchestheURL.

ThemajorityofframeworksuseacombinationofatechniqueknownashashnavigationandtheusageoftheHTML5HistoryAPItohandlechangesintheURLwithoutreloadingthepage.

InanSPA,thelinksusuallycontainthehash(#)character.ThischaracterwasoriginallydesignedtosetthefocusononeoftheDOMelementsonapage,butitisusedbyMV*frameworkstonavigatewithoutneedingtofullyreloadthewebpage.

Inordertounderstandthisconcept,wearegoingtoimplementareallybasicRouterfromscratch.Wearegoingtostartbytakingalookathowaroute—aplainobjectusedtorepresentaURL—looksinthemajorityofMV*frameworks:

classRoute{

publiccontrollerName:string;

publicactionName:string;

publicargs:Object[];

constructor(controllerName:string,actionName:string,args:Object[]){

this.controllerName=controllerName;

this.actionName=actionName;

this.args=args;

}

}

Therouterobservesthechangesinthewebbrowser'sURL.WhentheURLchanges,therouterparsesitandgeneratesanewrouteinstance.

Areallybasicroutercouldlookasfollows:

classRouter{

private_defaultController:string;

private_defaultAction:string;

constructor(defaultController:string,defaultAction:string){

this._defaultController=defaultController||"home";

this._defaultAction=defaultAction||"index";

}

publicinitialize(){

//observeURLchangesbyusers

$(window).on('hashchange',()=>{

varr=this.getRoute();

this.onRouteChange(r);

});

}

Page 363: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

//EncapsulatesreadingtheURL

privategetRoute(){

varh=window.location.hash;

returnthis.parseRoute(h);

}

//EncapsulatesparsinganURL

privateparseRoute(hash:string){

varcomp,controller,action,args,i;

if(hash[hash.length-1]==="/"){

hash=hash.substring(0,hash.length-1);

}

comp=hash.replace("#",'').split('/');

controller=comp[0]||this._defaultController;

action=comp[1]||this._defaultAction;

args=[];

for(i=2;i<comp.length;i++){

args.push(comp[i]);

}

returnnewRoute(controller,action,args);

}

privateonRouteChange(route:Route){

//invokecontrollerhere!

}

}

Note

Inthesecondpartofthischapter,wearegoingtodevelopanentireSPAframeworkfromscratch,andwewilluseanextendedversionoftheprecedingclass.

Theprecedingclasstakesthenameofthedefaultconstructorandthenameofthedefaultactionasitsconstructorarguments.Thecontrollernamedhomeandtheactionnamedindexareusedasthedefaultvalueswhennoargumentsarepassedtotheconstructor.

Themethodnamedinitializeisusedtocreateaneventlistenerforthehashchangeevent.Webbrowserstriggerthiseventwhenthewindow.location.hashvaluechanges.

Forexample,let'sconsiderthecurrentURLtobehttp://localhost:8080.Auserthenclicksonthefollowinglink:

<ahref="#tasks/index">ViewTasks</a>

Whenthelinkisclicked,thewindow.location.hashvaluewillchangeto"task/index".TheURLinthebrowsernavigationpanelwillchange,butthehashcharacterwillpreventthepagefromfullyreloading.TherouterwilltheninvokeitsgetRoutemethodtotransformtheURLintoanewinstanceoftheRouteclassbyusingtheparseRoutemethod.

TheURLfollowsthefollowingnameconvection:

Page 364: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

#conrollerName/actionName/arg1/arg2/arg3/argN

Thismeansthatthetask/indexURListransformedinto:

newRoute("task","index",[]);

Note

ThemajorityofMV*frameworksusetheHTMLHistoryAPItohidethehash(#)characterfromtheURL,butwewillnotimplementthisfeatureinourframework.

TheinstanceoftheRouteclassispassedtotheonRouteChangemethod,whichisresponsibleforinvokingthecontrollerthatmatchestheroute.

Note

WehaveomittedtheimplementationoftheonRouteChangemethodonpurposebutwillrefertothisfunctionintheMediatorandDispatchersectionslaterinthischapter.

Thisisbasicallyhowhashnavigationandrouterswork.Aswecanexpect,inarealframework,arouterhasmanyadditionalfeatures,buttheprecedingexampleshouldhelpusgainagoodunderstandingofhowroutingworksinthemajorityofMV*frameworks.

Page 365: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MediatorSomeMV*frameworksintroduceacomponentknownasMediator.Themediatorisasimpleobjectallothermodulesusetocommunicatewitheachother.

Themediatorusuallyimplementsthepublish/subscribedesignpattern(alsoknownaspub/sub).Thispatternenablesmodulestonotdependoneachother.Insteadofmakingdirectuseofotherpartsoftheapplication,modulescommunicatethroughevents.

Modulescanlistenforandreacttoeventsbutalsopublisheventsoftheirowntogiveothermodulesthechancetoreact.Thisensuresloosecouplingofapplicationmodules,whilestillallowingforeaseofinformationexchange.

Themediatorcanalsohelpustoallowdeveloperstoextendourframework(bysubscribingtoevents)withoutactuallyhavingtomodifytheframeworkitself.AswesawinChapter4,Object-OrientedProgrammingwithTypeScript,thisisagoodthingbecauseitadherestotheOpen/CloseprincipleintheSOLIDprinciples.

Wearegoingtoavoidtheinternaldetailsofhowamediatorworksfornow,butwecantakealookatanexampleofthepublicinterfaceofamediator:

interfaceIMediator{

publish(e:IAppEvent):void;

subscribe(e:IAppEvent):void;

unsubscribe(e:IAppEvent):void;

}

Intheprevioussection,weomittedthedetailsabouthowtherouterinvokesacontrollerbecausetheframeworkthatwearegoingtodevelopwilluseamediator:

classRouter{

//...

privateonRouteChange(route:Route){

this.meditor.publish(newAppEvent("app.dispatch",route,null));

}

}

Theprecedingcodesnippetshowcaseshowtherouteravoidsinvokingthecontroller'sactiondirectly,andinstead,itpublishesaneventusingamediator.

Page 366: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DispatcherTherewassomethinginthepreviouscodesnippetthatmayhavecaughtyourattention:theeventnameisapp.dispatch.

Theapp.dispatcheventreferstoanentityknownasDispatcher.Thismeansthattherouterissendinganeventtothedispatcherandnottoacontroller:

classDispatcher{

//...

publicinitialize(){

this.meditor.subscribe(

newAppEvent("app.dispatch",null,(e:any,data?:any)=>{

this.dispatch(data);

})

);

}

//Createanddisposecontrollerinstances

privatedispatch(route:IRoute){

//1.Disposepreviouscontroller

//2.Createinstanceofnewcontroller

//3.InvokecontrolleractionusingMediator

}

//...

}

Aswecanseeinthiscodesnippet,thedispatcherisresponsibleforthecreationofnewcontrollersandthedisposalofoldcontrollers.WhenarouterfinishesparsingaURL,itwillpassaninstanceoftheRouteclasstothedispatcherusingamediator.Thedispatcherthendisposesthepreviouscontrollercreatesaninstanceofthenewcontroller,andinvokesthecontrolleractionusingamediator.

Page 367: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Client-siderenderingandVirtualDOMWearealreadyfamiliarwiththebasicsofclient-siderendering.Weknowclient-siderenderingrequiresatemplateandsomedatatogenerateHTMLasoutput,butwehaven'tmentionedsomeperformancedetailsthatweneedtoconsiderwhenselectinganMV*framework.

ManipulatingtheDOMisoneofthemainpotentialperformancebottlenecksinSPAs.Forthisreason,itisinterestingtocomparehowframeworksrendertheviewsinternallybeforewedecidetoworkwithoneoranother.

Someframeworksrenderaviewwheneverthemodelchanges,andtherearetwopossiblewaystoknowwhenamodelhaschanged:

Thefirstoneistocheckforchangesusinganinterval(thisoperationissometimesreferredasadirtycheck)Thesecondoptionistouseanobservablemodel

Theobservableapproachismuchmoreefficientthanusingatimeintervalbecausetheobservablemodelwillonlyconsumeprocessingtimewhenithasactuallychanged.Ontheotherhand,theintervalwillconsumeprocessingtimeevenwhenthemodelhasnotchanged.

Whentorenderisimportant,butwealsoneedtoconsiderhowtorender.SomeframeworksmanipulatetheDOMdirectlyandothersuseanin-memoryrepresentationoftheDOMknownasVirtualDOM.VirtualDOMismuchmoreefficientbecauseJavaScriptisabletomanipulatethein-memoryrepresentationoftheDOMmuchfasterthantheDOMitself.

Page 368: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UserinterfacedatabindingUserinterface(UI)databindingisadesignpatternthataimstosimplifydevelopmentofgraphicUIapplications.UIdatabindingbindsUIelementstoanapplicationdomainmodel.

Abindingcreatesalinkbetweentwopropertiessuchthatwhenonechanges,theotheroneisupdatedtothenewvalueautomatically.Bindingscanconnectpropertiesonthesameobject,oracrosstwodifferentobjects.MostMV*frameworksincludesomesortofbindingimplementationbetweenviewsandmodels.

One-waydatabinding

One-waydatabindingisatypeofUIdatabinding.Thistypeofdatabindingonlypropagateschangesinonedirection.

InthemajorityofMV*frameworks,thismeansthatanychangesinthemodelarepropagatedtotheview.Ontheotherhand,anychangesintheviewarenotpropagatedtothemodel.

Two-waydatabinding

Two-waybindingisusedtoensurethatanychangestotheviewarepropagatedtothemodelandanychangesinthemodelarepropagatedtotheview.

Page 369: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management
Page 370: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DataflowSomeofthelatestMV*frameworkshaveintroducednewapproachesandtechniques.Oneofthesenewconceptsistheunidirectionaldataflowarchitecture(introducedbyFlux).

Thisunidirectionaldataflowarchitectureisbasedontheideathatchangingthevalueofavariableshouldautomaticallyforcerecalculationofthevaluesofvariablesthatdependonitsvalue.

InanMVCapplication,acontrollerhandlesmultipleModelsandViews.Sometimes,aViewusesmorethanonemodel,andwhentwo-waydatabindingisused,wecanendupwithacomplicatedflowofdatatofollow.Thefollowingdiagramillustratessuchascenario:

Note

Inthisdiagram,actiondoesnotrefertotheactionsinacontroller.Actionherereferstouserorapplicationevents.

Dataflowarchitectureattemptstosolvethisproblembyrestrictingtheflowofdatatooneuniquechannelanddirection.Bydoingso,theflowofdatawithintheapplicationcomponentsbecomesmucheasiertofollow.Thefollowingdiagramillustratestheflowofdatainanapplicationthatusesunidirectionaldataflowarchitecture:

Page 371: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theprecedingdiagramillustrateshowthedataalwaysmovesinthesamedirection.

InFlux'sunidirectionaldataflowarchitecture,alltheactionsaredirectedtothedispatcher.ThedispatcherinFluxislikethedispatcherinourframework,butinsteadofpassingtheexecutionflowtoacontroller,itpassestheexecutionflowtoastore.

StoresareinchargeofretrievingandmanipulatingdataandcanbecomparedwithModelsinMVC.Oncethedatahasbeenmodifiedinsomeway,itispassedtotheviews.

Views,justlikeinMVC,areresponsibleforrenderingthedataasHTMLandhandlinguserevents(actions).Iftheeventrequiressomedatatobemodifiedinsomeway,theViewswillsendanactiontothedispatcherinsteadofmanipulatingitsmodel,aswouldhappeninanapplicationwithtwo-waydatabindingsupport.

Thedataalwaysmovesinthesamedirectionandincircles,whichmakestheexecutionflowofalargedataflowapplicationmucheasiertodebugandpredictthanthatofatwo-waydatabindingMVCapplication.

Page 372: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WebcomponentsandshadowDOMSomeframeworksusethetermwebcomponenttorefertoreusableuserinterfacewidgets.WebcomponentsallowdeveloperstodefinecustomHTMLelements.Forexample,wecoulddefineanewHTML<map>tagtodisplayamap.Webcomponentscanimporttheirowndependenciesanduseclient-sidetemplatestorendertheirownHTMLusingatechnologyknownasshadowDOM.

ShadowDOMallowsthebrowsertouseHTML,CSS,andJavaScriptwithinawebcomponent.ShadowDOMisusefulwhendevelopinglargeapplicationsbecauseithelpstopreventCSS,HTML,andJavaScriptconflictsbetweencomponents.

Note

SomeoftheexistingMV*frameworks(forexample,Polymer)canbeusedtoimplementrealwebcomponents.Whileotherframeworks(forexample,React)usethetermwebcomponentstorefertoreusableuserinterfacewidgets,thosecomponentscannotbeconsideredrealwebcomponentsbecausetheydon'tusethewebcomponentstechnologystack(customelements,HTMLtemplates,shadowDOMandHTMLimports).

Page 373: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChoosinganapplicationframeworkWecancreateaSPAfromscratch,butusuallywepickupanexistingframeworkbeforecreatingourown.OneofthemainproblemsofchoosingaJavaScriptSPAframeworkisthattherearetoomanychoices.

ThelatestandgreatestJavaScriptframeworkcomesaroundeverysixteenminutes.

--AllenPike

Iwouldpersonallyrecommendconsideringaframeworkoranotherdependingonthefeaturesthatyouthinkthatyouwillneedtoachieveyourgoals.

Forexample,ifwearegoingtoworkonanapplicationwithnotreallycomplexviewsandforms,Backbone.jsoroneofitsderivations(Marionette.js,Chaplin,andsoon)shouldworkforus.However,ifourapplicationisexpectedtohavemanyformsandcomplexviews,Ember.jsorAngularJSmightbeabetteroption.

Note

Ifyouneedsomeextrahelpwhenchoosingoneframeworkoveranother,youshouldvisithttp://todomvc.com.TodoMVCisaprojectthatoffersthesameapplication(ataskmanager)implementedusingMV*conceptsinmostofthepopularJavaScriptMV*frameworkstoday.

Page 374: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WritinganMVCframeworkfromscratchNowthatwehaveagoodideaaboutthecommoncomponentsofanMV*applicationframework,wearegoingtotrytoimplementourownframeworkfromscratch.

Note

Theframeworkthatweareabouttodevelophasnotbeendesignedtobeusedinarealprofessionalenvironment.RealMV*frameworkshavethousandsoffeaturesandhavebeenunderintensedevelopmentformonthsandevenyearsbeforebecomingstable.

ThisframeworkhasbeendevelopednottobethemostefficientorthemostmaintainableMV*frameworkavailable,buttobeagoodlearningresource.

Ourapplicationwillfeaturecontrollers,templates,views,andmodelsaswellasarouter,amediator,andadispatcher.Let'stakealookattheroleofeachofthesecomponentsinourframework:

Application:Thisistherootcomponentofanapplication.Theapplicationcomponentisinchargeoftheinitializationofalltheinternalcomponentsoftheframework(mediator,router,anddispatcher).Mediator:Themediatorisinchargeofthecommunicationbetweenalltheothercomponentsintheapplication.ApplicationEvents:Applicationeventsareusedtosendinformationfromonecomponenttoanother.Anapplicationeventisidentifiedbyanidentifierknownasatopic.Thecomponentscanpublishapplicationeventsaswellassubscribeandunsubscribetoapplicationevents.Router:TherouterobservesthechangesinthebrowserURLandcreatesinstancesoftheRouteclassthatarethensenttotheDispatcherusinganapplicationevent.Routes:TheseareusedtorepresentaURL.TheURLsusenamingconventionsthatcanbeusedtoidentifywhichcontrollerandactionshouldbeinvoked.Dispatcher:ThedispatcherreceivesinstancesoftheRouteclass,whichareusedtoidentifytherequiredcontroller.Thedispatchercanthendisposethepreviouscontrollerandcreateanewcontrollerinstanceifnecessary.Oncethecontrollerhasbeeninitialized,thedispatcherpassestheexecutionflowtothecontrollerusinganapplicationevent.Controllers:Controllersareusedtoinitializeviewsandmodels.Oncetheviewsandmodelsareinitialized,thecontrollerpassestheexecutionflowtooneormoremodelsusinganapplicationevent.Models:ModelsareinchargeoftheinteractionwiththeHTTPAPIaswellasdatamanipulationinmemory.Thisinvolvesdataformattingaswellasoperationssuchastheadditionordeletionofdata.OncetheModelhasfinishedmanipulatingthedata,itispassedtooneormoreviewsusinganapplicationevent.Views:Viewsareinchargeoftheloadandcompilationoftemplates.Oncethetemplatehasbeenloaded,theviewswaitfordatatobesentbythemodels.Whenthedataisreceived,itiscombinedwiththetemplatestogenerateHTMLcode,whichisappendedto

Page 375: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

theDOM.ViewsarealsoinchargeofthebindingandunbindingofUIevents(click,focus,andsoon).

Thefollowingdiagramcanhelpustounderstandtheinteractionbetweentheavailablecomponents:

Nowthatwehaveabasicideaabouttheoverallarchitectureofourframework,let'sstartanewproject.

Page 376: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrerequisitesJustlikewehavebeendoinginthepreviouschaptersofthisbook,itisrecommendedtocreateanewprojectandconfigureanautomateddevelopmentworkflowusingGulp.

Youcantrytocreatetheframeworkandfinalapplicationfollowingthestepsdescribedinthefollowingsections,oryoucandownloadthecompanionsourcecodetogetacopyofthefinishedapplication.

Wearegoingtostartbyinstallingthefollowingruntimedependencieswithnpm:

npminit

npminstallanimate.cssbootstrapdatatableshandlebarsjqueryq--save

Wealsoneedtoinstallthefollowingdevelopmentdependencies:

npmbrowser-syncbrowserifychaigulpgulp-coverallsgulp-tslintgulp-

typescriptgulp-uglifykarmakarma-chaikarma-mochakarma-sinonmocharun-

sequencesinonvinyl-buffervinyl-source-stream--save-dev

Now,let'sinstalltherequiredtypedefinitionfilesusingtsd:

tsdinit

tsdinstalljquerybootstraphandlebarsqchaisinonmochajquery.dataTables

highcharts--save

Theapplicationusesthefollowingdirectorytree:

├──LICENSE

├──README.md

├──css

│└──site.css

├──data

│├──nasdaq.json

│└──nyse.json

├──gulpfile.js

├──index.html

├──karma.conf.js

├──node_modules

├──package.json

├──source

│├──app

││└──//Chapter10

│└──framework

│├──app.ts

│├──app_event.ts

│├──controller.ts

│├──dispatcher.ts

│├──event_emitter.ts

│├──framework.ts

│├──interfaces.ts

│├──mediator.ts

│├──model.ts

Page 377: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

│├──route.ts

│├──router.ts

│├──tsconfig.json

│└──view.ts

├──test

├──tsd.json

└──typings

Wewillbeworkingonthefileslocatedunderthesourcefolderduringthischapter.Inthenextchapter,wewillcreateanapplicationusingourframework.Mostofthefilesofthisapplicationwillbelocatedundertheappfolder.

Nowthatwehaveabasicideaabouttheoverallarchitectureofourframework,let'sproceedtoimplementeachofitscomponents.

Note

Thefinalversionoftheentireframeworkandapplicationisincludedinthecompanionsourcecode.

Page 378: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ApplicationeventsWearegoingtouseapplicationeventsthatallowthecommunicationbetweentwocomponents.Forexample,whenamodelfinishesreceivingtheresponseofanHTTPAPI,theresponseoftherequestwillbesentfromthemodeltoaviewusinganapplicationevent.

AswesawinChapter4,Object-OrientedProgrammingwithTypeScript,oneoftheSOLIDprinciplesisthedependencyinversionprinciple,whichstatesthatweshouldnotdependuponconcretions(classes)andshoulddependuponabstractionsinstead(interfaces).WearegoingtotrytofollowtheSOLIDprinciples,solet'sgetstartedbycreatinganewfilenamedinterfaces.tsinsidetheframeworkfolderanddeclaringtheIAppEventinterface:

interfaceIAppEvent{

topic:string;

data:any;

handler:(e:any,data:any)=>void;

}

Anapplicationeventcontainsanidentifierortopicandsomedataoraneventhandler.Wewillunderstandthesepropertiesbetteroncewegettopublishandsubscribetosomeevents.

Let'scontinuebycreatinganewfilenamedapp_event.tsinsidetheframeworkfolderandcopythefollowingcodeintoit:

///<referencepath="./interfaces"/>

classAppEventimplementsIAppEvent{

publicguid:string;

publictopic:string;

publicdata:any;

publichandler:(e:Object,data?:any)=>void;

constructor(topic:string,data:any,handler:(e:any,data?:any)=>

void){

this.topic=topic;

this.data=data;

this.handler=handler;

}

}

export{AppEvent};

TheprecedingcodesnippetdeclaresaclassnamedAppEventwhichimplementstheIappEventinterface.

Page 379: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MediatorAswealreadyknow,themediatorisacomponentthatimplementsthepub/subdesignpatternandisusedtoavoidthedirectcommunicationbetweentwocomponents.

Let'saddanewinterfacetotheinterfaces.tsfile:

interfaceIMediator{

publish(e:IAppEvent):void;

subscribe(e:IAppEvent):void;

unsubscribe(e:IAppEvent):void;

}

Aswecanseeinthiscodesnippet,theIMediatorinterfaceexposesthethreemethodsnecessarytoimplementthepublish/subscribedesignpattern,asfollows:

publish:Thisisusedtotriggerevents.Whenwepublishanevent,alltheeventsubscribersreceiveit.subscribe:Thisisusedtosubscribetoanevent,orinotherwords,setaneventhandlerforanevent.unsubscribe:Thisisusedtounsubscribetoanevent,orinotherwords,removeaneventhandlerforaneventtype.

Now,let'sproceedtocreateanewfilenamedmediator.tsundertheframeworkfolderandaddthefollowingcodetoit:

///<referencepath="./interfaces"/>

classMediatorimplementsIMediator{

private_$:JQuery;

private_isDebug;

constructor(isDebug:boolean=false){

this._$=$({});

this._isDebug=isDebug;

}

publicpublish(e:IAppEvent):void{

if(this._isDebug===true){console.log(newDate().getTime(),"PUBLISH",

e.topic,e.data);}

this._$.trigger(e.topic,e.data);

}

publicsubscribe(e:IAppEvent):void{

if(this._isDebug===true){console.log(newDate().getTime(),"SUBSCRIBE",

e.topic,e.handler);}

this._$.on(e.topic,e.handler);

}

publicunsubscribe(e:IAppEvent):void{

if(this._isDebug===true){console.log(newDate().getTime(),

"UNSUBSCRIBE",e.topic,e.data);}

Page 380: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this._$.off(e.topic);

}

}

export{Mediator};

TheprecedingcodesnippetdeclaresaclassnamedMediator,whichimplementstheIMediatorinterface.TheMediatorconstructorhasadefault(false)parameterthatisusedtoindicateifweareusingthedebugmode.

Thedebugmodeisusefulbecausewhenitisenabled,wewillbeabletoobserveallthecallstothepublish,subscribe,andunsubscribemethodsofthemediatorwithouttheneedtouseadebugger.Inthefollowingscreenshot,wecanobservethekindofinformationthatwecanexpecttoseeinthebrowserconsolewhenthedebugmodeisenabled:

Thepublish,subscribe,andunsubscribemethodsusethejQuerytrigger,on,andoffmethodsrespectivelytoexecuteeventlistenersaswellascreateandremoveeventlistenerswhenrequested.

Thedefaultconstructoralsoinitializesaprivatepropertynamed_$.ThevalueofthispropertyisjustanemptyjQueryobjectinmemory.ThisobjectisusedbyjQuerytoaddandremoveeventhandlerswhenthetrigger,on,andoffmethodareinvoked.

Itimportanttomentionthatifthemediatorisclearedfrommemory,its_$propertywillalsobeclearedfrommemoryandalltheapplicationeventhandlerswillbelost.Inthefollowingsection,wewillseehowtheAppclassensuresthatthemediatorisneverclearedfrommemory.

Page 381: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ApplicationTheapplicationclassistherootcomponentofanapplication.Theapplicationclassisinchargeoftheinitializationofthemaincomponentsofanapplication(router,mediator,anddispatcher).

Wearegoingtostartbydeclaringacoupleofinterfacesrequiredbytheapplicationclass,solet'saddthefollowinginterfacestotheinterfaces.tdfile:

interfaceIAppSettings{

isDebug:boolean,

defaultController:string;

defaultAction:string;

controllers:Array<IControllerDetails>;

onErrorHandler:(o:Object)=>void;

}

interfaceIControllerDetails{

controllerName:string;

controller:{new(...args:any[]):IController;};

}

TheIAppSettingsinterfaceisusedtoindicatetheavailableapplicationsettings.Wecanusetheapplicationsettingstoenablethedebugmode,setthenameofthedefaultcontrollerandaction,settheavailablecontrollers,andsetaglobalerrorhandler.Let'stakealookattheactualimplementationoftheapplicationclass:

///<referencepath="./interfaces"/>

import{Dispatcher}from"./dispatcher";

import{Mediator}from"./mediator";

import{AppEvent}from"./app_event";

import{Router}from"./router";

classApp{

private_dispatcher:IDispatcher;

private_mediator:IMediator;

private_router:IRouter;

private_controllers:IControllerDetails[];

private_onErrorHandler:(o:Object)=>void;

constructor(appSettings:IAppSettings){

this._controllers=appSettings.controllers;

this._mediator=newMediator(appSettings.isDebug||false);

this._router=newRouter(this._mediator,appSettings.defaultController,

appSettings.defaultAction);

this._dispatcher=newDispatcher(this._mediator,this._controllers);

this._onErrorHandler=appSettings.onErrorHandler;

}

publicinitialize(){

this._router.initialize();

this._dispatcher.initialize();

Page 382: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this._mediator.subscribe(newAppEvent("app.error",null,(e:any,data?:

any)=>{

this._onErrorHandler(data);

}));

this._mediator.publish(newAppEvent("app.initialize",null,null));

}

}

export{App};

TheprecedingcodesnippetdeclaresaclassnamedAppthattakestheimplementationofIAppSettingsasitsonlyconstructorargument.Theclassconstructorinitializestheclassproperties(dispatcher,mediator,router,controllerandglobalerrorhandler).

Whenwecreateanewapplication,itautomaticallycreatesanewmediator,anditispassedtoboththerouterandthedispatcher.Thismeansthatoneuniqueinstanceofthemediatorissharedbyallthecomponentsintheapplication,orinotherwords,themediatorisasingleton:itstaysinmemoryfortheentireapplicationlifecycle.

AftercreatinganinstanceoftheAppclass,wemustinvoketheinitializemethodtostarttheexecutionoftheapplication.Wewilllaterseethatwhentherouterisinitialized,itusesthemediatortosubscribetotheapp.initializeevent.

Theinitializemethodcallstheinitializemethodofsomeoftheapplicationcomponents(routeranddispatcher).Itthensetsaneventhandlerforglobalerrorsandpublishestheapp.initializeevent.

Themediatortheninvokestheeventhandlerfortheapp.initializeeventbytherouter.Thisexplainshowtheexecutionflowispassedfromtheapplicationclasstotherouterclass.

Page 383: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RouteInordertobeabletounderstandtheimplementationoftherouterclass,weneedtolearnaboutsomeofitsdependenciesfirst.ThefirstofthesedependenciesistheRouteclass.

TheRouteclassimplementstheRouteinterface.Thisinterfacewaspreviouslyexplainedinthischapter,sowewillnotgointoitsdetailsagain.

interfaceIRoute{

controllerName:string;

actionName:string;

args:Object[];

serialize():string;

}

WehavealsoincludedtheimplementationoftheRouteclasspreviouslyinthischapter,butthemethodnamedserializewasomittedonpurpose.TheserializemethodtransformsaninstanceoftheRouteclassintoaURL.

///<referencepath="./interfaces"/>

classRouteimplementsIRoute{

publiccontrollerName:string;

publicactionName:string;

publicargs:Object[];

constructor(controllerName:string,actionName:string,args:Object[]){

this.controllerName=controllerName;

this.actionName=actionName;

this.args=args;

}

publicserialize():string{

vars,sargs;

sargs=this.args.map(a=>a.toString()).join("/");

s=`${this.controllerName}/${this.actionName}/${sargs}`;

returns;

}

}

export{Route};

Page 384: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

EventemitterTherouteralsohasadependencyintheEventEmitterclass.Thisclassisparticularlyimportantbecauseeverysinglecomponent(excepttheapplicationcomponent)intheentireframeworkextendsit.

Aswealreadyknow,allthecomponentsuseamediatortocommunicatewitheachother.Themediatorisasingleton,whichmeansthateverysinglecomponentinourapplicationneedstobeprovidedwithaccesstothemediatorinstance.

TheEventEmitterclassisusedtoreducetheamountofboilerplatecodethatisnecessarytoachievethisandtoprovidedeveloperswithsomehelpersthatfacilitatethepublicationandsubscriptionofmultipleapplicationevents:

interfaceIEventEmitter{

triggerEvent(event:IAppEvent);

subscribeToEvents(events:Array<IAppEvent>);

unsubscribeToEvents(events:Array<IAppEvent>);

}

Now,let'screateafilenamedevent_emitter.tsundertheframeworkdirectoryandcopythefollowingcodeintoit:

///<referencepath="./interfaces"/>

import{AppEvent}from"./app_event";

classEventEmitterimplementsIEventEmitter{

protected_metiator:IMediator;

protected_events:Array<IAppEvent>;

constructor(metiator:IMediator){

this._metiator=metiator;

}

publictriggerEvent(event:IAppEvent){

this._metiator.publish(event);

}

publicsubscribeToEvents(events:Array<IAppEvent>){

this._events=events;

for(vari=0;i<this._events.length;i++){

this._metiator.subscribe(this._events[i]);

}

}

publicunsubscribeToEvents(){

for(vari=0;i<this._events.length;i++){

this._metiator.unsubscribe(this._events[i]);

}

}

}

Page 385: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

export{EventEmitter};

WhenthesubscribeToEventsmethodisinvoked,the_eventspropertyisusedtostoretheeventstowhichacomponentissubscribed.

WhenacomponentdecidestoremoveitseventhandlersbyusingtheunsubscribeToEventsmethod,wedon'tneedtopassthefulllistofeventsagainbecausetheeventemitterusestheeventspropertytorememberthem.

Page 386: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RouterTherouterobservestheURLforchangesandgeneratesinstancesoftheRouteclassthatarethenpassedtothedispatcherusinganapplicationevent.TheRouterclassimplementstheIRouterinterface:

interfaceIRouterextendsIEventEmitter{

initialize():void;

}

Let'stakealookattheinternalimplementationoftheRouterclass:

///<referencepath="./interfaces"/>

import{EventEmitter}from"./event_emitter";

import{AppEvent}from"./app_event";

import{Route}from"./route";

classRouterextendsEventEmitterimplementsIRouter{

private_defaultController:string;

private_defaultAction:string;

constructor(metiator:IMediator,defaultController:string,defaultAction:

string){

super(metiator);

this._defaultController=defaultController||"home";

this._defaultAction=defaultAction||"index";

}

publicinitialize(){

//observeURLchangesbyusers

$(window).on('hashchange',()=>{

varr=this.getRoute();

this.onRouteChange(r);

});

//beabletotriggerURLchanges

this.subscribeToEvents([

//usedtotriggerroutingonappstart

newAppEvent("app.initialize",null,(e:any,data?:any)=>{

this.onRouteChange(this.getRoute());

}),

//usedtotriggerURLchangesfromothercomponents

newAppEvent("app.route",null,(e:any,data?:any)=>{

this.setRoute(data);}),

]);

}

//EncapsulatesreadingtheURL

privategetRoute(){

varh=window.location.hash;

Page 387: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnthis.parseRoute(h);

}

//EncapsulateswrittingtheURL

privatesetRoute(route:Route){

vars=route.serialize();

window.location.hash=s;

}

//EncapsulatesparsinganURL

privateparseRoute(hash:string){

varcomp,controller,action,args,i;

if(hash[hash.length-1]==="/"){

hash=hash.substring(0,hash.length-1);

}

comp=hash.replace("#",'').split('/');

controller=comp[0]||this._defaultController;

action=comp[1]||this._defaultAction;

args=[];

for(i=2;i<comp.length;i++){

args.push(comp[i]);

}

returnnewRoute(controller,action,args);

}

//PasscontroltotheDispatcherviatheMediator

privateonRouteChange(route:Route){

this.triggerEvent(newAppEvent("app.dispatch",route,null));

}

}

export{Router};

Wehaveseenthisclasspreviouslyinthischapter,buttherearesomesignificantdifferenceshere.ThistimetheRouteclassextendstheEventEmitterclasstakesamediatorandthenamesofthedefaultcontrolleranddefaultactionasitsconstructorarguments.

TheinitializemethodnowincludesacalltothesubscribeToEventsmethod,whichisusedtoaddanapplicationeventhandlerfortheapp.initializeevent.ThiseventisusedtoensurethattherouterparsestheURLwhentheapplicationlaunchesforthefirsttime.TherouterobservestheURLforchanges,butwhentheapplicationislaunchedforthefirsttime,therearenochangesintheURL,andtheapplicationdoesnotinvokeanycontroller.Therouterusestheapp.initializeeventhandlertosolvethisproblem.

Therouterisalsosubscribedtotheapp.routeevent.TheeventhandlerofthiseventusesamethodnamedsetRoutetosetthebrowser'sURL.Theapp.routeapplicationeventisusedtoallowothercomponentstonavigatetoaroute.

Finally,wecanfindthemethodnamedparseRoute,whichisusedtotransformaURLintoaninstanceoftheRouteclass,andtheonRouteChangemethod,whichisusedtopublishanapp.dispatchapplicationevent.

Page 388: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DispatcherThedispatcherisacomponentusedtocreateanddisposecontrollerswhenneeded.Disposingcontrollersisimportantbecauseacontrollercanusealargenumberofmodelsandviews,whichcanconsumeaconsiderableamountofmemory.

Ifwehavemanycontrollers,theamountofmemoryconsumedcouldbecomeaperformanceissue.Oneofthemaingoalsofthedispatcheristopreventthispotentialissue.

ThedispatcherimplementstheIDispatcherandIEventEmitterinterfaces:

interfaceIDispatcherextendsIEventEmitter{

initialize():void;

}

Let'stakealookattheimplementationofthedispatcherclass:

///<referencepath="./interfaces"/>

import{EventEmitter}from"./event_emitter";

import{AppEvent}from"./app_event";

classDispatcherextendsEventEmitterimplementsIDispatcher{

private_controllersHashMap:Object;

private_currentController:IController;

private_currentControllerName:string;

constructor(metiator:IMediator,controllers:IControllerDetails[]){

super(metiator);

this._controllersHashMap=this.getController(controllers);

this._currentController=null;

this._currentControllerName=null;

}

Weshouldbestartingtobecomefamiliarwithhowthemediatorworksatthispoint.EverycomponentinheritsfromtheEventEmitterclassandusesitsmethodstosubscribetosomeeventsinthemethodnamedinitialize.

Laterinthischapter,wewillbeabletoobservethatsomeclasses(Controllers,Views,andModels)alsohaveamethodnameddispose,whichisusedtounsubscribetothemethodstowhichthecomponentsubscribedintheinitializemethod.

//listentoapp.dispatchevents

publicinitialize(){

this.subscribeToEvents([

newAppEvent("app.dispatch",null,(e:any,data?:any)=>{

this.dispatch(data);

})

]);

}

Page 389: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThishashmapisusedtobeabletofindacontrollerasfastaspossiblewhenanewrouteneedstobedispatchedThefollowingmethodisusedtogenerateahashmapthatusesthecontrollernameasthekeyandthecontrollerconstructorasvalues:

privategetController(controllers:IControllerDetails[]):Object{

varhashMap,hashMapEntry,name,controller,l;

hashMap={};

l=controllers.length;

if(l<=0){

this.triggerEvent(newAppEvent(

"app.error",

"Cannotcreateanapplicationwithoutatleastonecontoller.",

null));

}

for(vari=0;i<l;i++){

controller=controllers[i];

name=controller.controllerName;

hashMapEntry=hashMap[name];

if(hashMapEntry!==null&&hashMapEntry!==undefined){

this.triggerEvent(newAppEvent(

"app.error",

"Twocontrollercannotusethesamename.",

null));

}

hashMap[name]=controller.controller;

}

returnhashMap;

}

Thefollowingmethodisresponsibleforthecreation,initialization,anddisposalofcontrollerinstances;thecodeiscommentedtofacilitateitsunderstanding:

privatedispatch(route:IRoute){

varController=this._controllersHashMap[route.controllerName];

//trytofindcontroller

if(Controller===null||Controller===undefined){

this.triggerEvent(newAppEvent(

"app.error",

`Controllernotfound:${route.controllerName}`,

null));

}

else{

//createacontrollerinstance

varcontroller:IController=newController(this._metiator);

//actionisnotavailable

vara=controller[route.actionName];

if(a===null||a===undefined){

this.triggerEvent(newAppEvent(

"app.error",

`Actionnotfoundincontroller:${route.controllerName}-+

Page 390: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

${route.actionName}`,

null));

}

//actionisavailable

else{

if(this._currentController==null){

//initializecontroller

this._currentControllerName=route.controllerName;

this._currentController=controller;

this._currentController.initialize();

}

else{

//disposepreviouscontrollerifnotneeded

if(this._currentControllerName!==route.controllerName){

this._currentController.dispose();

this._currentControllerName=route.controllerName;

this._currentController=controller;

this._currentController.initialize();

}

}

//passflowfromdispatchertothecontroller

this.triggerEvent(newAppEvent(

`app.controller.${this._currentControllerName}.${route.actionName}`,

route.args,

null

));

}

}

}

}

export{Dispatcher};

Afterdisposingthepreviouscontroller(ifnecessary)andcreatinganewcontroller,thiscontrollerisinitialized.Whenacontrollerisinitialized,itsinitializemethodisinvoked,andasweknow,itisthenthatacomponentsubscribestosomeevents.

Whenthedispatcherpublishesthefollowingapplicationevent,thecontrollerisalreadysubscribedtoitandtheexecutionflowispassedtothecontroller'seventhandler:

`app.controller.${this._currentControllerName}.${route.actionName}`

Page 391: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ControllerControllersareinchargeoftheinitializationanddisposalofviewsandmodels.Sincecontrollersmustbedisposablebythedispatcher,acontrollermustimplementthedisposemethodfromtheIControllerinterface:

interfaceIControllerextendsIEventEmitter{

initialize():void;

dispose():void;

}

ThemodelsandviewsaresetaspropertiesoftheclassesthatextendtheControllerclass.TheControllerclassitselfdoesnotprovideuswithanyfunctionality,asitismeanttobeimplementedbydeveloperswhenworkingonanapplication.

///<referencepath="./interfaces"/>

import{EventEmitter}from"./event_emitter";

import{AppEvent}from"./app_event";

classControllerextendsEventEmitterimplementsIController{

constructor(metiator:IMediator){

super(metiator);

}

publicinitialize():void{

thrownewError('Controller.prototype.initialize()isabstractyoumust

implementit!');

}

publicdispose():void{

thrownewError('Controller.prototype.dispose()isabstractyoumust

implementit!');

}

}

export{Controller};

Eventhoughitisnotforcedbytheframework,itisrecommendedyouusethemediatortopassthecontroltooneofthemodels(notviews)fromthecontroller.

Page 392: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ModelandmodelsettingsModelsareusedtointeractwithawebserviceandtransformthedatareturnedbyit.Modelsallowustoread,format,update,ordeletethedatareturnedbyawebservice.ModelsimplementtheIModelandIEventEmitterinterfaces:

interfaceIModelextendsIEventEmitter{

initialize():void;

dispose():void;

}

AmodelneedstobeprovidedwiththeURLofthewebservicethatitconsumes.WearegoingtouseaclassdecoratornamedModelSettingstosettheURLoftheservicetobeconsumed.

WecouldinjecttheserviceURLviaitsconstructor,butitisconsideredabadpracticetoinjectdata(asopposedtoabehavior)viaaclassconstructor.Thedecoratorincludessomecommentstofacilitateitsunderstanding:

///<referencepath="./interfaces"/>

import{EventEmitter}from"./event_emitter";

functionModelSettings(serviceUrl:string){

returnfunction(target:any){

//saveareferencetotheoriginalconstructor

varoriginal=target;

//autilityfunctiontogenerateinstancesofaclass

functionconstruct(constructor,args){

varc:any=function(){

returnconstructor.apply(this,args);

}

c.prototype=constructor.prototype;

varinstance=newc();

instance._serviceUrl=serviceUrl;

returninstance;

}

//thenewconstructorbehaviour

varf:any=function(...args){

returnconstruct(original,args);

}

//copyprototypesointanceofoperatorstillworks

f.prototype=original.prototype;

//returnnewconstructor(willoverrideoriginal)

returnf;

}

}

Inthenextchapter,wewillbeabletoapplythedecoratorasfollows:

Page 393: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

@ModelSettings("./data/nasdaq.json")

classNasdaqModelextendsModelimplementsIModel{

//...

Let'stakealookattheinternalimplementationoftheModelclass:

classModelextendsEventEmitterimplementsIModel{

//thevaluesof_serviceUrlmustbesetusingtheModelSettingsdecorator

private_serviceUrl:string;

constructor(metiator:IMediator){

super(metiator);

}

//mustbeimplementedbyderivedclasses

publicinitialize(){

thrownewError('Model.prototype.initialize()isabstractandmust

implemented.');

}

//mustbeimplementedbyderivedclasses

publicdispose(){

thrownewError('Model.prototype.dispose()isabstractandmust

implemented.');

}

protectedrequestAsync(method:string,dataType:string,data){

returnQ.Promise((resolve:(r)=>{},reject:(e)=>{})=>{

$.ajax({

method:method,

url:this._serviceUrl,

data:data||{},

dataType:dataType,

success:(response)=>{

resolve(response);

},

error:(...args:any[])=>{

reject(args);

}

});

});

}

protectedgetAsync(dataType:string,data:any){

returnthis.requestAsync("GET",dataType,data);

}

protectedpostAsync(dataType:string,data:any){

returnthis.requestAsync("POST",dataType,data);

}

protectedputAsync(dataType:string,data:any){

returnthis.requestAsync("PUT",dataType,data);

}

Page 394: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

protecteddeleteAsync(dataType:string,data:any){

returnthis.requestAsync("DELETE",dataType,data);

}

}

export{Model,ModelSettings};

Justlikeinthecaseofthecontrollers,theinitializeanddisposemethodsaremeanttobeimplementedbythederivedmodels,sotheydon'tcontainanylogichere.

TherequestAsyncmethodisusedtoretrievedatafromawebserviceorstaticfile.Aswecansee,themethodusesthejQueryAJAXAPIandQ'sPromises.

TheclassalsoincludesthegetAsync,postAsync,putAsync,anddeleteAsyncmethods,whicharehelperstoperformGET,POST,PUT,andDELETErequestsrespectively.

Eventhoughitisnotforcedbytheframework,itisrecommendedyouusethemediatortopassthecontroltooneoftheviewsfromthemodel.

Page 395: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ViewandviewsettingsViewsareusedtorendertemplatesandhandleUIevents.Justliketherestofthecomponentsinourapplication,theViewclassextendstheEventEmitterclass:

interfaceIViewextendsIEventEmitter{

initialize():void;

dispose():void;

}

AviewneedstobeprovidedwiththeURLofthetemplatethatitconsumes.WearegoingtouseaclassdecoratornamedViewSettingstosettheURLofthetemplatetobeconsumed.

WecouldinjectthetemplateURLviaitsconstructor,butitisconsideredabadpracticetoinjectdata(asopposedtoabehavior)viaaclassconstructor.Thedecoratorincludessomecommentstofacilitateitsunderstanding:

///<referencepath="./interfaces"/>

import{EventEmitter}from"./event_emitter";

import{AppEvent}from"./app_event";

functionViewSettings(templateUrl:string,container:string){

returnfunction(target:any){

//saveareferencetotheoriginalconstructor

varoriginal=target;

//autilityfunctiontogenerateinstancesofaclass

functionconstruct(constructor,args){

varc:any=function(){

returnconstructor.apply(this,args);

}

c.prototype=constructor.prototype;

varinstance=newc();

instance._container=container;

instance._templateUrl=templateUrl;

returninstance;

}

//thenewconstructorbehaviour

varf:any=function(...args){

returnconstruct(original,args);

}

//copyprototypesoinstanceofoperatorstillworks

f.prototype=original.prototype;

//returnnewconstructor(willoverrideoriginal)

returnf;

}

}

Inthenextchapter,wewillbeabletoapplythedecoratorasfollows:

Page 396: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

@ViewSettings("./source/app/templates/market.hbs","#outlet")

classMarketViewextendsViewimplementsIView{

//...

Let'stakealookattheViewclass.Justlikeinthecaseofthecontrollersandmodels,theinitializeanddisposemethodsaremeanttobeimplementedbythederivedviews,sotheydon'tcontainanylogichere.

classViewextendsEventEmitterimplementsIView{

//thevaluesof_containerand_templateUrlmustbesetusingthe

ViewSettingsdecorator

protected_container:string;

private_templateUrl:string;

private_templateDelegate:HandlebarsTemplateDelegate;

constructor(metiator:IMediator){

super(metiator);

}

//mustbeimplementedbyderivedclasses

publicinitialize(){

thrownewError('View.prototype.initialize()isabstractandmust

implemented.');

}

//mustbeimplementedbyderivedclasses

publicdispose(){

thrownewError('View.prototype.dispose()isabstractandmust

implemented.');

}

Theviewclassincludestwonewmethods(namedbindDomEventsandunbindDomEvents)thatmustbeimplementedbytheirderivedclasses.Aswecanguessfromtheirnames,thesemethodsshouldbeusedtoset(bindDomEvents)andunset(unbindDomEvents)UIeventhandlers:

//mustbeimplementedbyderivedclasses

protectedbindDomEvents(model:any){

thrownewError('View.prototype.bindDomEvents()isabstractandmust

implemented.');

}

//mustbeimplementedbyderivedclasses

protectedunbindDomEvents(){

thrownewError('View.prototype.unbindDomEvents()isabstractandmust

implemented.');

}

Thefollowingasynchronousmethodsusepromisesandareusedtoloadatemplate(loadTemplateAsync),compileit(compileTemplateAsync),cacheit(getTemplateAsync),andrenderit(renderAsync)—allthemethodsareprivateexceptrenderAsync,whichismeantobe

Page 397: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

usedbythederivedviews:

//asynchroniuslyloadsatemplate

privateloadTemplateAsync(){

returnQ.Promise((resolve:(r)=>{},reject:(e)=>{})=>{

$.ajax({

method:"GET",

url:this._templateUrl,

dataType:"text",

success:(response)=>{

resolve(response);

},

error:(...args:any[])=>{

reject(args);

}

});

});

}

//asynchroniuslycompileatemplate

privatecompileTemplateAsync(source:string){

returnQ.Promise((resolve:(r)=>{},reject:(e)=>{})=>{

try{

vartemplate=Handlebars.compile(source);

resolve(template);

}

catch(e){

reject(e);

}

});

}

//asynchroniuslyloadsandcompileatemplateifnotdonealready

privategetTemplateAsync(){

returnQ.Promise((resolve:(r)=>{},reject:(e)=>{})=>{

if(this._templateDelegate===undefined||this._templateDelegate===

null){

this.loadTemplateAsync()

.then((source)=>{

returnthis.compileTemplateAsync(source);

})

.then((templateDelegate)=>{

this._templateDelegate=templateDelegate;

resolve(this._templateDelegate);

})

.catch((e)=>{reject(e);});

}

else{

resolve(this._templateDelegate);

}

});

}

//asynchroniuslyrenderstheview

protectedrenderAsync(model){

returnQ.Promise((resolve:(r)=>{},reject:(e)=>{})=>{

this.getTemplateAsync()

Page 398: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

.then((templateDelegate)=>{

//generatehtmlandappendtotheDOM

varhtml=this._templateDelegate(model);

$(this._container).html(html);

//passmodeltoresolvesoitcanbeusedby

//subviewsandDOMeventinitializer

resolve(model);

})

.catch((e)=>{reject(e);});

});

}

}

export{View,ViewSettings};

Page 399: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FrameworkTheframeworkfileisusedtoprovideaccesstoallthecomponentsintheframeworkfromonesinglefile.Thismeansthatwhenweimplementanapplicationusingourframework,wewillnotneedtoimportadifferentfileforeachcomponent:

///<referencepath="./interfaces"/>

import{App}from"./app";

import{Route}from"./route";

import{AppEvent}from"./app_event";

import{Controller}from"./controller";

import{View,ViewSettings}from"./view";

import{Model,ModelSettings}from"./model";

export{App,AppEvent,Controller,View,ViewSettings,Model,ModelSettings,

Route};

Page 400: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,weunderstoodwhatasingle-pagewebapplicationis,whatitscommoncomponentsare,andwhatthemaincharacteristicsofthisarchitectureare.

WealsocreatedourownMV*framework.ThispracticalexperienceandknowledgewillhelpustounderstandmanyoftheavailableMV*frameworks.

Inthenextchapter,wewilltrytoputinpracticemanyoftheconceptsthatwehavelearnedinthisbookbycreatingafullSPAusingtheframeworkthatwecreatedinthischapter.

Page 401: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter10.PuttingEverythingTogetherInthischapter,wearegoingtoputintopracticethemajorityoftheconceptsthatwehavecoveredinthepreviouschapters.

Wewilldevelopasmallsingle-pagewebapplicationusingtheSPAframeworkthatwedevelopedinChapter9,ApplicationArchitecture.

ThisapplicationwillallowustofindouthowtheNASDAQandNYSEstocksaredoingonaparticularday.Itwillnotbeaverylargeapplication,butitwillbebigenoughtodemonstratetheadvantagesofworkingwithTypeScriptandusingagoodapplicationarchitecture.

Wewillwritesomeclassesandseveralfunctions.Someofthesefunctionswillbeasynchronous(Chapter1,IntroducingTypeScript;Chapter3,WorkingwithFunctions;Chapter4,Object-OrientedProgrammingwithTypeScript;andChapter5,Runtime).WewillalsoconsumesomedecoratorsprovidedbyourSPAframework(Chapter8,Decorators).

Tocompletethechapter,wewillcreateanautomatedbuildtofacilitatethedevelopmentprocess(Chapter2,AutomatingYourDevelopmentWorkflow),improvetheapplicationperformance(Chapter6,ApplicationPerformance),andensurethatitworkscorrectlybywritingsomeunitandintegrationtests(Chapter7,ApplicationTesting).

Inthischapter,wewillaimtohelpyougainconfidencewithTypeScriptandtheSPAarchitecture.WeneedtofocusontheSOLIDprinciplesandtheseparationofconcerns.Ourgoalistocreateanapplicationthatismaintainableandtestable,andanapplicationthatcangrowovertimeandwhichcomponentscanbereusedinfutureapplications.

Page 402: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrerequisitesInthisapplication,wewillusethetoolsandthedirectorytreethatwecreatedinthepreviouschapter.Youcanusethetsd.jsonandpackage.jsonfilesincludedinthecompanionsourcecodetoinstalltherequirednpmpackagesandtypedefinitionfiles.RefertotheprerequisitessectionundertheWritinganMVCframeworkfromscratchsectioninChapter9,ApplicationArchitecture,foradditionalinformationabouttheprerequisitesofthisapplication.

Page 403: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theapplication'srequirementsWewilldevelopasmallapplicationthatwillallowuserstoseealistofstocksymbols.Astocksymbolrepresentsacompanythattradesitssharesonastockexchange.

Theapplicationhomepagewilldisplaystocksymbolsfromtwopopularstockexchanges:NASDAQ(NationalAssociationofSecuritiesDealersAutomatedQuotations)andNYSE(NewYorkstockexchange).

Asyoucanseeinthefollowingscreenshot,thewebapplicationrequiresatopmenucontaininglinksthatallowtheusertoseethestocksymbolsinoneoftheaforementionedstockexchanges.Thelistofstocksymbolswillbedisplayedinatable,whichwillincludesomebasicdetailsaboutthestocks,suchasthepriceofashareinthelastsaleorthenameorthecompany:

Thelastcolumninthetablecontainssomebuttonsthatwillallowuserstonavigatetoasecondscreenthatdisplaysastockquote.Astockquoteisjustasummaryofthepricingperformancedetailsofthestockforagivenperiodoftime.

Thestockquotescreenwilldisplayalinegraphthatisusedbythebrokerstoseehowthepriceoftheshares(theyaxis)hasevolvedovertime(thexaxis).Wecandisplaymultiplelinestovisualizetheevolutionoftheopeningprice(thepriceofthesharesatthebeginningoftheday),theclosingprice(thepriceofashareattheendoftheday),thehighprice(the

Page 404: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

highestsellingpriceoftheshareinagivenday),andthelowprice(thelowestsellingpriceoftheshareinagivenday).

Page 405: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theapplication'sdataAsweexplainedinthepreviouschapter,weneedanapplicationbackendthatallowsustoquerythedatafromawebbrowserusingAJAXrequestsinordertodevelopanSPA.ThismeansthatwearegoingtoneedanHTTPAPI.

WewilluseafreelyavailablepublicHTTPAPIthatwillallowustoobtainrealstockquotedata.Forthelistofavailablestocksymbols,wewillusestaticJSONfiles.TheseJSONfileshavebeengeneratedbytransformingaCSVfileavailableontheNASDAQwebsite.TheexternalHTTPAPIwillalsoprovidethelinegraphdata.

Intotal,wewillbeusingthreesetsofdata:

Marketdata:ThisdataisstoredinstaticJSONfiles.ThesefileshavebeengeneratedfromaCSVfileprovidedbytheNASDAQofficialwebsiteandcanbefoundinthecompanionexample.Stockquotedata:Thishasbeenprovidedbyanexternalwebservice.TheexternaldataproviderthatwewilluseinthisexampleisacompanycalledMarkit,specializinginfinancialinformationservices.WewillusetheirmarketdataAPI(v2),whichisavailableforfreeandhasbeenwelldocumentedathttp://dev.markitondemand.com/.Chartdata:ThisisalsoprovidedinawebservicebyMarkit.

Page 406: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theapplication'sarchitectureWewilldevelopanSPAusingourownframework.Aswesawinthepreviouschapter,ourframeworkcanmapaURLwithanactioninacontroller.

Ourapplicationwillhavethreemainscreens.EachscreenusesadifferentURL,asfollows:

#market/nasdaqdisplaysstocksintheNASDAQstockmarket#market/nysedisplaysstocksintheNYSEstockmarket#symbol/quote/{symbol}displaysastockquotefortheselectedstocksymbol

EachofthemainURLsmentionedearlierwillbeimplementedasacontroller'sactioninourapplication.Inthepreviouschapter,yousawthatURLsadheretothefollowingnamingconvention:#controllerName/actionName/arg1/arg2/argN.

IfweextrapolatethisnamingconventiontotheURLsmentionedintheprecedinglist,wecandeducethatourapplicationwillhavetwocontrollers:MarketControllerandSymbolController.

TheMarketControllercontrollerwillbeimplementedusingtwomodelsandoneview:

NasdaqModel:ThisloadsalistofNASDAQstocksfromastaticJSONfileNyseModel:ThisloadsalistofNYSEstocksfromastaticJSONfileMarketView:ThisrendersthelistofeithertheNASDAQorNYSEstocks

Eachcomponentcommunicateswiththeotherusingapplicationeventsandthemediator.Theexecutionorderofthemarketscreenlooksasfollows:

TheSymbolControllercontrollerwillbeimplementedusingtwomodelsandtwoviews:

QuoteModel:ThisloadsastockquotefortheselectedsymbolChartModel:ThisloadssymbolperformancedatapointsforthelastyearChartView:ThisdisplaysstockperformanceinaninteractivechartSymbolView:Thisdisplaysthelastpricechangefortheselectedsymbol

Eachcomponentcommunicateswiththeotherusingapplicationeventsandthemediator.Theexecutionorderofthestockquotescreenlooksasfollows:

Page 407: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management
Page 408: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theapplication'sfilestructurePresentedinthissectionisthefolderstructureoftheapplicationwearegoingtobuild.Intherootdirectory,youcanfindtheapplicationaccesspoint(index.html),aswellassomeoftheautomationtools'configurationfiles(gulpfile.js,karma.conf.js,package.json,andsoon).Youcanalsoobservethetypingsfolder,whichcontainssometypedefinitionfiles.

Justasinthepreviouschapters,theapplicationsourcecodeislocatedunderthesourcedirectory.Theunitandintegrationtestsarelocatedinthetestfolder.Thefollowingisthefolderstructureoftheapplication:

├──LICENSE

├──README.md

├──css

│└──site.css

├──data

│├──nasdaq.json

│└──nyse.json

├──gulpfile.js

├──index.html

├──karma.conf.js

├──node_modules

├──package.json

├──source

│├──app

││├──controllers

│││├──market_controller.ts

│││└──symbol_controller.ts

││├──main.ts

││├──models

│││├──chart_model.ts

│││├──nasdaq_model.ts

│││├──nyse_model.ts

│││└──quote_model.ts

││├──templates

│││├──market.hbs

│││└──symbol.hbs

││└──views

││├──chart_view.ts

││├──market_view.ts

││└──symbol_view.ts

│└──framework

│└──framework.ts(Chapter9)

├──test

│├──app

│└──framework

├──tsd.json

└──typings

Underthesourcedirectory,youcanobservetwofolders,namedappandframework.Wecreatedallthefilesundertheframeworkdirectoryinthepreviouschapter.Thistime,wewillfocusontheapplication,whichmeanswewillbeworkingundertheappdirectorymostofthe

Page 409: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

time.

Insidetheappdirectory,youcanfindsomedirectoriesnamedcontrollers,models,templates,andviews.Asyoucanguess,thesedirectoriesareusedtostorecontrollers,models,templates,andviewsrespectively.

Youcanalsofindthemain.tsfileinsidetheappdirectory.Thisfileistheapplication'sentrypoint,butbecausewearegoingtouseES6modules,wearenotgoingtobeabletoloadthisfileinawebbrowserusinga<script/>tag.

Page 410: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringtheautomatedbuildJustaswedidinChapter2,AutomatingYourDevelopmentWorkflow,weneedtocreateaconfigurationfiletoconfigurethedesiredGulptasks.Solet'screateafilenamedgulpfile.jsandimporttherequiredGulpplugins:

vargulp=require("gulp"),

browserify=require("browserify"),

source=require("vinyl-source-stream"),

buffer=require("vinyl-buffer"),

tslint=require("gulp-tslint"),

tsc=require("gulp-typescript"),

karma=require("karma").server,

coveralls=require('gulp-coveralls'),

uglify=require("gulp-uglify"),

runSequence=require("run-sequence"),

header=require("gulp-header"),

browserSync=require("browser-sync"),

reload=browserSync.reload,

pkg=require(__dirname+"/package.json");

Weneedtorememberthatbeforewecanimportoneofthesepackages,wemustfirstinstallthemusingnpm.

Oncethepluginshavebeenimported,wecanproceedtowriteourfirsttask,whichisusedtocheckforsomebasicnameconventionrulesandtoavoidsomebadpractices(theTypeScriptfilesareunderthesourceandtestsdirectories):

gulp.task("lint",function(){

returngulp.src([

"source/**/**.ts",

"test/**/**.test.ts"

])

.pipe(tslint())

.pipe(tslint.report("verbose"));

});

WealsoneedanothertasktocompileourTypeScriptcodeintoJavaScriptcode.Asweareworkingwithdecorators,weneedtoensurethatweareusingTypeScript1.5orhigherandthattheexperimentalDecoratorscompilersettingsandtargetareconfiguredasinthefollowingcodesnippet:

vartsProject=tsc.createProject({

target:"es5",

module:"commonjs",

experimentalDecorators:true,

typescript:typescript

});

Oncewehavesetupthecompileroptions,wecanproceedtowritesometasks.Thefirstonewillcompiletheapplicationcode:

Page 411: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

gulp.task("build",function(){

returngulp.src("src/**/**.ts")

.pipe(tsc(tsProject))

.js.pipe(gulp.dest("build/source/"));

});

Thesecondonewillcompiletheunittestandintegrationtestcode.Weneedtouseanewprojectobjecttoavoidpotentialruntimeissues:

vartsTestProject=tsc.createProject({

target:"es5",

module:"commonjs",

experimentalDecorators:true,

typescript:typescript

});

gulp.task("build-test",function(){

returngulp.src("test/**/*.test.ts")

.pipe(tsc(tsTestProject))

.js.pipe(gulp.dest("/build/test/"));

});

ThetwoprevioustasksshouldbeenoughtogenerateJavaScript,butbecauseweareusingCommonJSmodules,weneedtowriteatasktobundletheCommonJSmodulesintoapackagethatcanbeloadedandexecutedinawebbrowser.AswesawinChapter2,AutomatingYourDevelopmentWorkflow,wewillcreateafewGulptasksthatuseBrowserifyforthispurpose.

Weneedatasktobundletheapplicationcode:

gulp.task("bundle-source",function(){

varb=browserify({

standalone:'TsStock',

entries:"build/source/app/main.js",

debug:true

});

returnb.bundle()

.pipe(source("bundle.js"))

.pipe(buffer())

.pipe(gulp.dest("bundled/source/"));

});

Wefurtherneedatasktobundletheapplication'sunittests:

gulp.task("bundle-unit-test",function(){

varb=browserify({

standalone:'test',

entries:"build/test/bdd.test.js",

debug:true

});

returnb.bundle()

.pipe(source("bdd.test.js"))

Page 412: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

.pipe(buffer())

.pipe(gulp.dest("bundled/test/"));

});

Weneedafinaltasktobundletheapplication'sintegrationtests:

gulp.task("bundle-e2e-test",function(){

varb=browserify({

standalone:'test',

entries:"build/test/e2e.test.js",

debug:true

});

returnb.bundle()

.pipe(source("e2e.test.js"))

.pipe(buffer())

.pipe(gulp.dest("bundled/e2e-test/"));

});

Wewillreturntothegulpfile.jsconfigurationfilelaterinthischaptertoaddsomeadditionaltasksthatwillbeinchargeofrunningtheapplicationanditsautomatedtests,aswellassomeoptimizations.

Note

Untilnow,wehavebeenworkingontheconfigurationofanautomateddevelopmentworkflow.Fromnowon,wewillfocusontheapplicationcomponents.Acomponentiscomposedoffourcoreelements:template,stylerules,services,andthecomponent'slogic.Youwillbeabletofindthestylerulesandtemplatesinthecompanioncodesamples,butwewillmainlyfocusontheTypeScriptfiles(servicesandthecomponent'slogic)here.

Page 413: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Theapplication'slayoutLet'screateanewfile,namedindex.html,undertheapplication'srootdirectory.Thefollowingcodesnippetisanalteredversionoftherealindex.htmlpage,whichisincludedwiththecompanionsourcecode:

<ulclass="navnavbar-nav">

<li>

<ahref="#market/nasdaq">NASDAQ</a>

</li>

<li>

<ahref="#market/nyse">NYSE</a>

</li>

</ul>

<divid="outlet">

<!--HTMLGENERATEDBYVIEWSGOESHERE-->

</div>

AsyoucanseeintheprecedingHTMLsnippet,thecodehastwoimportantelements.ThefirstsignificantelementistheURLofthetwolinks.Theselinksincludethehashcharacter(#),andtheywillbeprocessedbytheapplication'srouter.

ThesecondsignificantelementistheelementthatusesoutletasID.ThisnodeisusedbyourframeworkasacontainerwheretheDOMofeachnewpageisdynamicallygeneratedandaddedtothepage.

Page 414: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingtherootcomponentAsyousawinthepreviouschapter,therootcomponentofourcustomMVCframeworkistheAppcomponent.So,let'screateanewfile,namedmain.ts,underthesource/appdirectory.

Wecanaccessalltheinterfacesintheframeworkbyaddingareferencetothesource/interfaces.tsasfollows:

///<referencepath="../framework/interfaces"/>

Wecanthenaccessallthecomponentsintheframeworkbyimportingtheframework/framework.tsfile:

import{App,View}from"../framework/framework";

Ourapplicationwillhavetwocontrollers.Thefilesdon'texistyetbutwecanaddthetwoimportstatementsanyway:

import{MarketController}from"./controllers/market_controller";

import{SymbolController}from"./controllers/symbol_controller";

Atthispoint,weneedtocreateanobjectliteralthatimplementstheIAppSettingsinterface.Thisobjectallowsustosetsomebasicconfiguration,suchasthenameofthedefaultcontrolleroraction,oraglobalerrorhandler.However,themostimportantfieldintheobjectliteralisthecontrollerfield,whichmustbeanarrayofIControllerDetails.IfyouneedadditionaldetailsabouttheIControllerDetails,refertothepreviouschapter.

varappSettings:IAppSettings={

isDebug:true,

defaultController:"market",

defaultAction:"nasdaq",

controllers:[

{controllerName:"market",controller:MarketController},

{controllerName:"symbol",controller:SymbolController}

],

onErrorHandler:function(e:Object){

alert("Sorry!therehasbeenanerrorpleasecheckouttheconsoleformore

info!");

console.log(e.toString());

}

};

WecanthencreatetheAppinstanceandinvoketheinitializemethodtostartexecutingit:

varmyApp=newApp(appSettings);

myApp.initialize();

Atthispoint,ourcodedoesnotcompilebecausewehavenotdefinedtheMarketControllerandSymbolControllercontrollersyet.Let'sdefineourfirstcontroller.

Page 415: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthemarketcontrollerLet'screateanewfilenamedmarket_controller.tsundertheapp/controllersdirectory.WeneedtoimporttheControllerandAppEvententitiesfromtheframeworkalongwithsomeentitiesthatarenotavailableyet(NyseModel,NasdaqModelandMarketView).

///<referencepath="../../framework/interfaces"/>

import{Controller,AppEvent}from"../../framework/framework";

import{MarketView}from"../views/market_view";

import{NasdaqModel}from"../models/nasdaq_model";

import{NyseModel}from"../models/nyse_model";

Inanapplicationthatusesourframework,acontrollermustextendthebaseControllerclassandimplementtheIControllerclass:

classMarketControllerextendsControllerimplementsIController{

Wearenotforcedtodeclaretheviewsandmodelsusedbythecontrollerasitsproperties,butitisrecommended:

private_marketView:IView;

private_nasdaqModel:IModel;

private_nyseModel:IModel;

Itisalsorecommendedthatyousetthevalueofallthecontroller'sdependenciesinsidethecontrollerconstructor:

constructor(metiator:IMediator){

super(metiator);

this._marketView=newMarketView(metiator);

this._nasdaqModel=newNasdaqModel(metiator);

this._nyseModel=newNyseModel(metiator);

}

Note

Insteadofsettingthevalueofallthecontroller'sdependenciesinsidethecontrollerconstructor,itwouldbeevenbettertouseanIoCcontainertoautomaticallyinjectthecontroller'sdependenciesviaitsconstructor.Though,implementinganIoCcontainerisnotasimpletask,itisbeyondthescopeofthisbook.

Wemustimplementtheinitializemethod.Theinitializemethodistheplacewhereacontrollershoulddothefollowing:

Subscribetooneapplicationeventforeachactionavailableinthecontroller.Inthiscase,thecontrollerhastwoactions(thenasdaqandnysemethods).InitializeviewsbyinvokingtheView.initialize()method.Inthiscase,thereisonlyoneview(marketView).InitializemodelsbyinvokingtheModel.initialize()method.Inthiscase,therearetwo

Page 416: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

models(nasdaqModelandnyseModel).

publicinitialize():void{

//subscribetocontrolleractionevents

this.subscribeToEvents([

newAppEvent("app.controller.market.nasdaq",null,(e,args:

string[])=>{this.nasdaq(args);}),

newAppEvent("app.controller.market.nyse",null,(e,args:

string[])=>{this.nyse(args);})

]);

//initializeviewandmodelsevents

this._marketView.initialize();

this._nasdaqModel.initialize();

this._nyseModel.initialize();

}

Thedisposemethodistheoppositeoftheinitializemethod.Ifaneventhandlerwascreatedintheinitializemethod,itshouldbedestroyedinthedisposemethod.TheunsubscribeToEventshelperwillunsubscribealltheeventsthatweresubscribedusingthesubscribeToEventshelper:

//disposeviews/modelsandstoplisteningtocontrolleractions

publicdispose():void{

//disposethecontrollerevents

this.unsubscribeToEvents();

//disposeviewsandmodelevents

this._marketView.dispose();

this._nasdaqModel.dispose();

this._nyseModel.dispose();

}

Asyousawinthepreviouschapter,thedispatcherusesthecontroller'sinitializeanddisposemethodstofreesomememorywhenitisnotneededanymore.Ifweforgettodisposeoneoftheviewsusedbythecontrollerinitsdisposemethod,theviewcouldendupstayinginmemoryforever.

Theactionsofacontrollershouldnotperformanykindofdatamanipulation(modelsshouldbeinchargeofthat)oruserinterfaceeventsmanagement(viewsshouldbeinchargeofthat).Ideally,acontroller'sactionsshouldonlypublishoneormoreapplicationeventssotheexecutionflowgoesfromthecontrollertooneormoremodels.

Inthecaseofthenasdaqaction,thecontrollerpublishesoneoftheeventstowhichthenasdaqmodelsubscribedwhentheinitializemethodofNasdaqModelwasinvoked:

//displayNASDAQstocks

publicnasdaq(args:string[]){

this._metiator.publish(newAppEvent("app.model.nasdaq.change",null,

null));

Page 417: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

Inthecaseofthenyseaction,thecontrollerpublishesoneoftheeventstowhichthenysemodelwassubscribedwhentheinitializemethodofNyseModelwasinvoked:

//displayNYSEstocks

publicnyse(args:string[]){

this._metiator.publish(newAppEvent("app.model.nyse.change",null,null));

}

}

export{MarketController};

Page 418: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingtheNASDAQmodelLet'screateanewfilenamednasdaq_model.tsundertheapp/modelsdirectory.WecanthenimporttheModel,AppEvent,andModelSettingsfromourframeworkanddeclareanewclassnamedNasdaqModel.ThenewclassmustextendthebaseModelclassandimplementtheIModelinterface.

WewillalsousetheModelSettingsdecoratortoindicatethepathofawebserviceorstaticdatafile.Inthiscase,wewilluseastaticdatafile,whichcanbefoundinthecompanionsourcecode:

///<referencepath="../../framework/interfaces"/>

import{Model,AppEvent,ModelSettings}from"../../framework/framework";

@ModelSettings("./data/nasdaq.json")

classNasdaqModelextendsModelimplementsIModel{

constructor(metiator:IMediator){

super(metiator);

}

Themodelwillsubscribetotheapp.model.nasdaq.changeeventwhentheinitializemethodisinvoked.Thisisactuallytheeventthatthecontroller'sactionpublishedtopasstheexecutionflowfromthecontrollertothemodel:

//listentomodelevents

publicinitialize(){

this.subscribeToEvents([

newAppEvent("app.model.nasdaq.change",null,(e,args)=>{

this.onChange(args);})

]);

}

Justlikeinthepreviouscontroller,theunsubscribeToEventshelperwillunsubscribealltheeventsthatweresubscribedusingthesubscribeToEventshelper:

//disposemodelevents

publicdispose(){

this.unsubscribeToEvents();

}

Thisistheeventhandleroftheapp.model.nasdaq.changeevent.TheeventhandlerusesthegetAsyncmethodtoloadthedatafromtheserviceURLthatwepreviouslyspecifiedusingtheModelSettingsdecorator.ThegetAsyncmethodisinheritedfromthebaseModelclass,whichweimplementedinthepreviouschapter.

ThegetAsyncmethodreturnsapromise;ifthepromiseisfulfilled,thedataisformattedandthenpassedtoaview:

privateonChange(args):void{

Page 419: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.getAsync("json",args)

.then((data)=>{

//formatdata

varstocks={items:data,market:"NASDAQ"};

//passcontrolltothemarketview

this.triggerEvent(newAppEvent("app.view.market.render",stocks,

null));

})

.catch((e)=>{

//passcontroltotheglobalerrorhandler

this.triggerEvent(newAppEvent("app.error",e,null));

});

}

}

export{NasdaqModel};

Page 420: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingtheNYSEmodelLet'screateanewfilenamednyse_model.tsundertheapp/modelsdirectory.TheNyseModelclassisalmostidenticaltotheNasdaqModelclass,sowewillnotgointotoomuchdetail:

@ModelSettings("./data/nyse.json")

classNyseModelextendsModelimplementsIModel{

//...

}

export{NyseModel};

Allweneedtodoiscopythecontentsofthenasdaq_model.tsfileintothenyse_model.tsfileandreplace(casesensitive)nasdaqwithnyse.

Note

Thiskindofcodeduplicationisknownasacodesmell.Acodesmellindicatesthatsomethingiswrongandweneedtorefactor(improve)it.WecouldavoidalotofcodeduplicationbyusingGenerictypes.Howevergenerictypeswerenotusedherebecausewethoughthatshowcasingtheusageofdecoratorswouldbemorevaluableforthereadersofthisbook.

Page 421: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthemarketviewLet'screateanewfilenamedmarket_view.tsundertheapp/viewsdirectory.WecanthenimporttheAppEvent,ViewSettings,andRoutecomponentsfromourframeworkanddeclareanewclassnamedMarketView.ThenewclassmustextendthebaseViewclassandimplementtheIViewinterface.

WewillalsousetheViewSettingsdecoratortoindicatethepath,aHandlebarstemplate,andaselector,whichisusedtofindtheDOMelementthatwillbeusedastheparentnodeoftheview'sHTML:

///<referencepath="../../framework/interfaces"/>

import{View,AppEvent,ViewSettings,Route}from"../../framework/framework";

@ViewSettings("./source/app/templates/market.hbs","#outlet")

classMarketViewextendsViewimplementsIView{

constructor(metiator:IMediator){

super(metiator);

}

Thisviewissubscribedtotheapp.view.market.rendereventanditshandlerinvokestherenderAsyncmethod,whichhasbeeninheritedfromthebaseviewclass.Thismethodreturnsapromise,whichisfulfilledifthetemplatepassedtotheViewSettingsdecoratorhasbeenloadedandcompiledsuccessfully.

Forthepromisetobefulfilled,theviewmustbesuccessfullyrenderedandappendedtotheDOMelementthatmatchestheselectorpassedtotheViewSettingsdecorator:

initialize():void{

this.subscribeToEvents([

newAppEvent("app.view.market.render",null,(e,args:any)=>{

this.renderAsync(args)

.then((model)=>{

//setDOMevents

this.bindDomEvents(model);

})

.catch((e)=>{

//passcontroltotheglobalerrorhandler

this.triggerEvent(newAppEvent("app.error",e,null));

});

}),

]);

}

Justlikeinthepreviouscontrollerandmodel,theunsubscribeToEventshelperwillunsubscribealltheeventsthatweresubscribedtousingthesubscribeToEventshelper:

publicdispose():void{

this.unbindDomEvents();

Page 422: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.unsubscribeToEvents();

}

Viewsareresponsibleforthemanagementofuserevents.Thecomponentsinourframeworkusetheinitializemethodtosubscribetoapplicationevents,andthedisposemethodtounsubscribetoapplicationevents.Inthecaseofuserevents,wewillusethebindDomEventsmethodtosettheuserevents,andtheunbindDomEventsmethodtodisposeofthem:

//initializesDOMevents

protectedbindDomEvents(model:any){

varscope=$(this._container);

//handleclickon"quote"button

$(".getQuote").on('click',scope,(e)=>{

varsymbol=$(e.currentTarget).data('symbol');

this.getStockQuote(symbol);

});

//maketablesortableandsearchable

$(scope).find('table').DataTable();

}

//disposesDOMevents

protectedunbindDomEvents(){

varscope=this._container;

$(".getQuote").off('click',scope);

vartable=$(scope).find('table').DataTable();

table.destroy();

}

Oneoftheusereventsobservesclicksonthequotebuttons.Whentheeventistriggered,thefollowingeventhandlerisinvoked:

privategetStockQuote(symbol:string){

//navigatetorouteusingrouteevent

this.triggerEvent(newAppEvent(

"app.route",

newRoute("symbol","quote",[symbol]),

null));

}

}

Asyoucansee,thiseventhandlercreatesanewrouteandpublishesanapp.routeevent.ThiswillcausetheroutertonavigatetothequoteactionintheSymbolController:export{MarketView};

Page 423: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthemarkettemplateThetemplateloadedandcompiledbyMarketViewlooksasfollows:

<divclass="panelpanel-defaultfadeInUpanimated">

<divclass="panel-body">

<h2>{{market}}</h2>

<tableclass="tabletable-responsibletable-condensed">

<thead>

<tr>

<th>Symbol</th>

<th>Name</th>

<th>LastSale</th>

<th>MarketCapital</th>

<th>IPOyear</th>

<th>Sector</th>

<th>industry</th>

<th>Quote</th>

</tr>

</thead>

<tbody>

{{#eachitems}}

<tr>

<td><spanclass="labellabel-default">{{Symbol}}</span></td>

<td>{{{Name}}}</td>

<td>{{LastSale}}</td>

<td>{{MarketCap}}</td>

<td>{{IPOyear}}</td>

<td>{{Sector}}</td>

<td>{{industry}}</td>

<td>

<buttonclass="btnbtn-primarybtn-smgetQuote"data-symbol="

{{Symbol}}">

<spanclass="glyphiconglyphicon-stats"aria-hidden="true">

</span>

Quote

</button>

</td>

</tr>

{{/each}}

</tbody>

</table>

</div>

</div>

Page 424: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthesymbolcontrollerLet'screateanewfilenamedsymbol_controller.tsundertheapp/controllersdirectory.ThisfilewillcontainanewcontrollernamedSymbolController.TheimplementationofthiscontrollerislargelysimilartotheimplementationoftheMarketControllercontroller,sowearegoingtoavoidgoingintotoomuchdetail.

Themaindifferencebetweenthiscontrollerandthepreviouscontrolleristhatthenewcontrollerusestwonewmodels(QuoteModelandChartModel)andtwonewviews(SymbolViewandChartView):

///<referencepath="../../framework/interfaces"/>

import{Controller,AppEvent}from"../../framework/framework";

import{QuoteModel}from"../models/quote_model";

import{ChartModel}from"../models/chart_model";

import{SymbolView}from"../views/symbol_view";

import{ChartView}from"../views/chart_view";

classSymbolControllerextendsControllerimplementsIController{

private_quoteModel:IModel;

private_chartModel:IModel;

private_symbolView:IView;

private_chartView:IView;

constructor(metiator:IMediator){

super(metiator);

this._quoteModel=newQuoteModel(metiator);

this._chartModel=newChartModel(metiator);

this._symbolView=newSymbolView(metiator);

this._chartView=newChartView(metiator);

}

//initializeviews/modelsandstratlisteningtocontrolleractions

publicinitialize():void{

//subscribetocontrolleractionevents

this.subscribeToEvents([

newAppEvent("app.controller.symbol.quote",null,(e,symbol:string)

=>{this.quote(symbol);})

]);

//initializeviewandmodelsevents

this._quoteModel.initialize();

this._chartModel.initialize();

this._symbolView.initialize();

this._chartView.initialize();

}

//disposeviews/modelsandstoplisteningtocontrolleractions

publicdispose():void{

//disposethecontrollerevents

Page 425: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.unsubscribeToEvents();

//disposeviewsandmodelevents

this._symbolView.dispose();

this._quoteModel.dispose();

this._chartView.dispose();

this._chartModel.dispose();

}

ItisalsoimportanttonoticethatthequoteactionpassesthecontroltotheQuoteModelmodel:

publicquote(symbol:string){

this.triggerEvent(newAppEvent("app.model.quote.change",symbol,null));

}

}

export{SymbolController};

Page 426: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthequotemodelLet'screateanewfilenamedquote_model.tsundertheapp/modelsdirectory.Thisisthethirdmodelthatwehaveimplementedsofar.Thismeansthatyoushouldbefamiliarwiththebasicsalready,buttherearesomeminoradditionsinthisparticularmodel.Thefirstthingthatyouwillnoticeisthatthewebserviceisnolongerastaticfile:

///<referencepath="../../framework/interfaces"/>

import{Model,AppEvent,ModelSettings}from"../../framework/framework";

@ModelSettings("http://dev.markitondemand.com/Api/v2/Quote/jsonp")

classQuoteModelextendsModelimplementsIModel{

constructor(metiator:IMediator){

super(metiator);

}

//listentomodelevents

publicinitialize(){

this.subscribeToEvents([

newAppEvent("app.model.quote.change",null,(e,args)=>{

this.onChange(args);})

]);

}

//disposemodelevents

publicdispose(){

this.unsubscribeToEvents();

}

ThesecondthingthatyoushouldnoticeisthattheonChangefunctioninvokesanewfunction(formatModel)whenthepromisereturnedbygetAsyncisfulfilled:

privateonChange(args):void{

//formatargs

vars={symbol:args};

this.getAsync("jsonp",s)

.then((data)=>{

//formatdata

varquote=this.formatModel(data);

//passcontrolltothemarketview

this.triggerEvent(newAppEvent("app.view.symbol.render",quote,

null));

})

.catch((e)=>{

//passcontroltotheglobalerrorhandler

this.triggerEvent(newAppEvent("app.error",e,null));

});

}

Thenewfunctionjustformatstheresponseofthewebservicestobedisplayedinauser-

Page 427: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

friendlymanner.Wecouldhavedonethisformattinginsidethepromisefulfillmentcallback.Usingaseparatefunctionmakesthecodesignificantlycleaner.

privateformatModel(data){

data.Change=data.Change.toFixed(2);

data.ChangePercent=data.ChangePercent.toFixed(2);

data.Timestamp=newDate(data.Timestamp).toLocaleDateString();

data.MarketCap=(data.MarketCap/1000000).toFixed(2)+"M.";

data.ChangePercentYTD=data.ChangePercentYTD.toFixed(2);

return{quote:data};

}

}

export{QuoteModel};

Page 428: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthesymbolviewLet'screateanewfilenamedsymbol_view.tsundertheapp/viewsdirectory.TheSymbolViewviewreceivesthestockdataformattedbytheQuoteModelmodelthroughthemediatorusingtheapp.view.symbol.renderevent:

///<referencepath="../../framework/interfaces"/>

import{View,AppEvent,ViewSettings}from"../../framework/framework";

@ViewSettings("./source/app/templates/symbol.hbs","#outlet")

classSymbolViewextendsViewimplementsIView{

constructor(metiator:IMediator){

super(metiator);

}

ThisviewisjustlikeMarketView;itsubscribestosomeeventsusingtheinitializemethod,andlaterdisposesofthoseeventsusingthedisposemethod.TheSymbolViewviewcanalsoinitializeanddisposeofusereventsusingthebindDomEventsandunbindDomEventsmethods.

However,thereisonesignificantdifferencebetweenSymbolViewandMarketView.AfterthepromisereturnedbyrenderAsynchasbeenfulfilledandtheusereventshavebeeninitialized,theexecutionflowispassedtoanothermodelviatheapp.model.chart.changeevent.Atthispoint,thestockquotescreenisvisiblebutitismissingthechart.

initialize():void{

this.subscribeToEvents([

newAppEvent("app.view.symbol.render",null,(e,model:any)=>{

this.renderAsync(model)

.then((model)=>{

//setDOMevents

this.bindDomEvents(model);

//passcontroltochartView

this.triggerEvent(newAppEvent("app.model.chart.change",

model.quote.Symbol,null));

})

.catch((e)=>{

this.triggerEvent(newAppEvent("app.error",e,null));

});

}),

]);

}

publicdispose():void{

this.unbindDomEvents();

this.unsubscribeToEvents();

}

//initializesDOMevents

protectedbindDomEvents(model:any){

Page 429: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

varscope=$(this._container);

//setDOMeventshere

}

//disposesDOMevents

protectedunbindDomEvents(){

varscope=this._container;

//killDOMeventshere

}

}

export{SymbolView};

Page 430: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthechartmodelLet'screateanewfilenamedchart_model.tsundertheapp/modelsdirectory.Thisisthelastmodelthatwewillimplement:

///<referencepath="../../framework/interfaces"/>

import{Model,AppEvent,ModelSettings}from"../../framework/framework";

@ModelSettings("http://dev.markitondemand.com/Api/v2/InteractiveChart/jsonp")

classChartModelextendsModelimplementsIModel{

constructor(metiator:IMediator){

super(metiator);

}

//listentomodelevents

publicinitialize(){

this.subscribeToEvents([

newAppEvent("app.model.chart.change",null,(e,args)=>{

this.onChange(args);})

]);

}

//disposemodelevents

publicdispose(){

this.unsubscribeToEvents();

}

Thistime,wewillneedtoformatboththerequestandtheresponse.WeneedtoencodetherequestparameterbecausethewebservicerequiresagroupofsettingsthatcannotbesentasparametersintheURLwithoutencodingitfirst.

TheonChangemethodusesthebrowser'sJSON.stringifyfunctiontotransformtherequiredwebservicearguments(aJSONobject)intoastring.Thestringisthenencodedusingthebrowser'sencodeURIComponentfunctionsoitcanbeusedasaparameterintheURL.

TheresponseisformattedusingamethodnamedformatModel:

privateonChange(args):void{

//formatargs(moreinfoathttp://dev.markitondemand.com/)

varp={

Normalized:false,

NumberOfDays:365,

DataPeriod:"Day",

Elements:[

{Symbol:args,Type:"price",Params:["ohlc"]}

]

};

varqueryString="parameters="+encodeURIComponent(JSON.stringify(p));

Page 431: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.getAsync("jsonp",queryString)

.then((data)=>{

//formatdata

varchartData=this.formatModel(args,data);

//passcontrolltothemarketview

this.triggerEvent(newAppEvent("app.view.chart.render",chartData,

null));

})

.catch((e)=>{

//passcontroltotheglobalerrorhandler

this.triggerEvent(newAppEvent("app.error",e,null));

});

}

Thisfunctionisusedtoformattheresponsefromdev.markitondemand.com,soitcanbeusedbyHighchartswithease.Highchartsisalibrarythatallowustorendergraphsontheclientside:

privateformatModel(symbol,data){

//moreinfoathttp://dev.markitondemand.com/

//andhttp://www.highcharts.com/demo/line-time-series

varchartData={

title:symbol,

series:[]

};

varseries=[

{name:"open",data:data.Elements[0].DataSeries.open.values},

{name:"close",data:data.Elements[0].DataSeries.close.values},

{name:"high",data:data.Elements[0].DataSeries.high.values},

{name:"low",data:data.Elements[0].DataSeries.low.values}

];

for(vari=0;i<series.length;i++){

varserie={

name:series[i].name,

data:[]

}

for(varj=0;j<series[i].data.length;j++){

varval=series[i].data[j];

vard=newDate(data.Dates[j]).getTime();

serie.data.push([d,val]);

}

chartData.series.push(serie);

}

returnchartData;

}

}

export{ChartModel};

Page 432: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthechartviewLet'screateanewfilenamedchart_view.tsundertheapp/viewsdirectory.Thisisthelastviewthatwewillimplement.Thisviewisalmostidenticaltothepreviousones,butthereisonesignificantdifference.AsthechartisrenderedbyHighchartsandnotHandlebars,wewillavoidpassingatemplateURLtotheViewSettingsdecorator:

///<referencepath="../../framework/interfaces"/>

import{View,AppEvent,ViewSettings}from"../../framework/framework";

@ViewSettings(null,"#chart_container")

classChartViewextendsViewimplementsIView{

constructor(metiator:IMediator){

super(metiator);

}

TheChartViewviewissubscribedtotheapp.view.chart.renderevent.TheeventhandlerisinvokedwhentheChartModelmodelhasbeenloadedandformatted,butsincewedon'tneedtorenderaHandlebarstemplate,wewillnotinvoketherenderAsyncmethodhere(aswedidinallthepreviousviews),andwewillinvokeamethodnamedrenderChartinstead:

initialize():void{

this.subscribeToEvents([

newAppEvent("app.view.chart.render",null,(e,model:any)=>{

this.renderChart(model);

this.bindDomEvents(model);

}),

]);

}

publicdispose():void{

this.unbindDomEvents();

this.unsubscribeToEvents();

}

//initializesDOMevents

protectedbindDomEvents(model:any){

varscope=$(this._container);

//setDOMeventshere

}

//disposesDOMevents

protectedunbindDomEvents(){

varscope=this._container;

//killDOMeventshere

}

TherenderChartmethodusestheHighchartsAPI(http://api.highcharts.com/highcharts)totransformthedatareturnedbyChartModelintoanicelookinginteractivechart:

Page 433: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

privaterenderChart(model){

$(this._container).highcharts({

chart:{

zoomType:'x'

},

title:{

text:model.title

},

subtitle:{

text:'Clickanddragintheplotareatozoomin'

},

xAxis:{

type:'datetime'

},

yAxis:{

title:{

text:'Price'

}

},

legend:{

enabled:true

},

tooltip:{

shared:true,

crosshairs:true

},

plotOptions:{

area:{

marker:{

radius:0

},

lineWidth:0.1,

threshold:null

}

},

series:model.series

});

}

}

export{ChartView};

Page 434: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheapplicationWecantestthisapplicationusingthesamesetoftoolsthatweusedinthepreviouschaptersofthisbook.Asyoualreadyknow,inordertorunourunittest,weneedtocreateaGulptasklikethefollowingone:

gulp.task("run-unit-test",function(cb){

karma.start({

configFile:"karma.conf.js",

singleRun:true

},cb);

});

WehaveusedtheKarmatestrunner,andweneedtosetitsconfigurationusingthekarma.conf.jsfile.Thekarma.conf.jsfileisalmostidenticaltotheonethatweusedinChapter7,ApplicationTesting,andwillnotbeincludedhereforthesakeofbrevity.

Wealsoneedatasktorunsomeend-to-endtests:

gulp.task('run-e2e-test',function(){

returngulp.src('')

.pipe(nightwatch({

configFile:'nightwatch.json'

}));

});

Thenightwatch.jsonfileisalmostidenticaltheonethatweusedinChapter7,ApplicationTesting,andthuswillnotbeincludedhere.

Refertothecompanionsourcecodetoseethecontentofnightwatch.jsonandthekarma.conf.jsfile,aswellassomeexamplesofunittestsandE2Etests.

Page 435: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PreparingtheapplicationforaproductionreleaseNowthattheapplicationhasbeenimplementedandtested,wecanprepareitforreleaseinaproductionenvironment.

Inthissection,wewillimplementtwoGulptasks.ThefirsttaskisusedtocompresstheoutputJavaScriptcode.CompressingtheJavaScriptcodewillimproveboththeloadingandexecutionperformanceofourapplication:

gulp.task("compress",function(){

returngulp.src("bundled/source/bundle.js")

.pipe(uglify({preserveComments:false}))

.pipe(gulp.dest("dist/"))

});

ThesecondGulptaskthatwewillimplementisusedtoaddacopyrightheader.Thetaskusessomeofthefieldsfromthenpmconfigurationfile(package.json)togenerateastring,whichcontainsthecopyrightdetails.ThestringisthenaddedtothetopofthecompressedJavaScriptfilethatwasgeneratedbytheprevioustask:

gulp.task("header",function(){

varpkg=require("package.json");

varbanner=["/**",

"*<%=pkg.name%>v.<%=pkg.version%>-<%=pkg.description%>",

"*Copyright(c)2015<%=pkg.author%>",

"*<%=pkg.license%>",

"*<%=pkg.homepage%>",

"*/",

""].join("\n");

returngulp.src("dist/bundle.js")

.pipe(header(banner,{pkg:pkg}))

.pipe(gulp.dest("dist/"));

});

WecouldalsocreatesomeextraGulptaskstoimprovetheperformanceofourapplicationfurther.Forexample,wecouldcreateatasktogenerateacachemanifest(asimpletextfilethatliststheresourcesthebrowsershouldcacheforofflineaccess)toimplementclient-sidecaching.

Page 436: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wecreatedanMVCapplicationthatallowedustofindouthowtheNASDAQandNYSEstocksweredoingonaparticularday.Thisapplicationisasingle-pagewebapplication,anditsarchitecturemakesitscomponentseasytoextend,reuse,maintain,andtest.

Theapplicationshowcasesmanyoftheconceptsthatwecoveredinthepreviouschapters.Wecreatedanautomatedbuild,andweusedmanyfunctions,classes,modules,andothercorelanguagefeatures.Wealsousedmodulesandworkedwithsomeasynchronousfunctions,andweusedsomedecorators.Theautomatedbuildperformssometasksthatwillhelpustoimprovetheapplicationperformanceandensuresthatitworkscorrectly.

ThisapplicationisnotaverylargeJavaScriptapplication.However,theapplicationislargeenoughtoshowcasethewaysinwhichTypeScriptcanhelpusdevelopcomplexapplicationsthatarereadytogrowandadapttochangeswithease.

IhopeyouenjoyedthisbookandfeeleagertolearnmoreaboutTypeScript.

IfyouareupforachallengeandyouwouldliketoreinforceyourTypeScriptskills,trythefollowing:

Youcantrytoachieve100percenttestcoverageintheapplicationthatwehavedevelopedoverthelasttwochapters.YoucanimproveourcustomSPAtheframeworkandintroducefeaturessuchasusinganIoCcontainerorusingaunidirectionaldataflow.

YoucanalsovisittheTodoMVCwebsite(http://todomvc.com/)tofindexamplesofintegrationbetweenTypeScriptandpopularMV*frameworks,suchasEmber.jsorBackbone.js,tolearnhowtouseaproduction-readySPAframework.

Page 437: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Part2.Module2TypeScriptDesignPatterns

BoostyourdevelopmentefficiencybylearningaboutdesignpatternsinTypeScript

Page 438: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter1.ToolsandFrameworksWecouldalwaysusethehelpofrealcodetoexplainthedesignpatternswe'llbediscussing.Inthischapter,we'llhaveabriefintroductiontothetoolsandframeworksthatyoumightneedifyouwanttohavesomepracticewithcompleteworkingimplementationsofthecontentsofthisbook.

Inthischapter,we'llcoverthefollowingtopics:

InstallingNode.jsandTypeScriptcompilerPopulareditorsorIDEsforTypeScriptConfiguringaTypeScriptprojectAbasicworkflowthatyoumightneedtoplaywithyourownimplementationsofthedesignpatternsinthisbook

Page 439: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InstallingtheprerequisitesThecontentsofthischapterareexpectedtoworkonallmajorandup-to-datedesktopoperatingsystems,includingWindows,OSX,andLinux.

AsNode.jsiswidelyusedasaruntimeforserverapplicationsaswellasfrontendbuildtools,wearegoingtomakeitthemainplaygroundofcodeinthisbook.

TypeScriptcompiler,ontheotherhand,isthetoolthatcompilesTypeScriptsourcefilesintoplainJavaScript.It'savailableonmultipleplatformsandruntimes,andinthisbookwe'llbeusingtheNode.jsversion.

Page 440: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InstallingNode.jsInstallingNode.jsshouldbeeasyenough.Butthere'ssomethingwecoulddotominimizeincompatibilityovertimeandacrossdifferentenvironments:

Version:We'llbeusingNode.js6withnpm3built-ininthisbook.(ThemajorversionofNode.jsmayincreaserapidlyovertime,butwecanexpectminimumbreakingchangesdirectlyrelatedtoourcontents.Feelfreetotryanewerversionifit'savailable.)Path:IfyouareinstallingNode.jswithoutapackagemanager,makesuretheenvironmentvariablePATHisproperlyconfigured.

Openaconsole(acommandpromptorterminal,dependingonyouroperatingsystem)andmakesureNode.jsaswellasthebuilt-inpackagemanagernpmisworking:

$node-v

6.x.x

$npm-v

3.x.x

Page 441: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InstallingTypeScriptcompilerTypeScriptcompilerforNode.jsispublishedasannpmpackagewithcommandlineinterface.Toinstallthecompiler,wecansimplyusethenpminstallcommand:

$npminstalltypescript-g

Option-gmeansaglobalinstallation,sothattscwillbeavailableasacommand.Nowlet'smakesurethecompilerworks:

$tsc-v

Version2.x.x

Note

YoumaygetaroughlistoftheoptionsyourTypeScriptcompilerprovideswithswitch-h.Takingalookintotheseoptionsmayhelpyoudiscoversomeusefulfeatures.

Beforechoosinganeditor,let'sprintoutthelegendaryphrase:

1. Savethefollowingcodetofiletest.ts:

functionhello(name:string):void{

console.log(`hello,${name}!`);

}

hello('world');

2. Changetheworkingdirectoryofyourconsoletothefoldercontainingthecreatedfile,andcompileitwithtsc:

$tsctest.ts

3. Withluck,youshouldhavethecompiledJavaScriptfileastest.js.ExecuteitwithNode.jstogettheceremonydone:

$nodetest.js

hello,world!

Herewego,ontheroadtoretireyourCTO.

Page 442: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChoosingahandyeditorAcompilerwithoutagoodeditorwon'tbeenough(ifyouarenotabelieverofNotepad).ThankstotheeffortsmadebytheTypeScriptcommunity,thereareplentyofgreateditorsandIDEsreadyforTypeScriptdevelopment.

However,thechoiceofaneditorcouldbemuchaboutpersonalpreferences.Inthissection,we'lltalkabouttheinstallationandconfigurationofVisualStudioCodeandSublimeText.ButotherpopulareditorsorIDEsforTypeScriptwillalsobelistedwithbriefintroductions.

Page 443: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

VisualStudioCodeVisualStudioCodeisafreelightweighteditorwritteninTypeScript.Andit'sanopensourceandcross-platformeditorthatalreadyhasTypeScriptsupportbuilt-in.

YoucandownloadVisualStudioCodefromhttps://code.visualstudio.com/andtheinstallationwillprobablytakenomorethan1minute.

ThefollowingscreenshotshowsthedebugginginterfaceofVisualStudioCodewithaTypeScriptsourcefile:

ConfiguringVisualStudioCode

AsCodealreadyhasTypeScriptsupportbuilt-in,extraconfigurationsareactuallynotrequired.ButiftheversionofTypeScriptcompileryouusetocompilethesourcecodediffersfromwhatCodehasbuilt-in,itcouldresultinunconformitybetweeneditingandcompiling.

Tostayawayfromtheundesiredissuesthiswouldbring,weneedtoconfigureTypeScriptSDKusedbyVisualStudioCodemanually:

1. PressF1,typeOpenUserSettings,andenter.VisualStudioCodewillopenthesettings

Page 444: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

JSONfilebythesideofaread-onlyJSONfilecontainingallthedefaultsettings.2. Addthefieldtypescript.tsdkwiththepathofthelibfolderundertheTypeScript

packagewepreviouslyinstalled:

1.Executethecommandnpmroot-ginyourconsoletogettherootofglobalNode.jsmodules.

2.Appendtherootpathwith/typescript/libastheSDKpath.

Note

YoucanalsohaveaTypeScriptpackageinstalledlocallywiththeproject,andusethelocalTypeScriptlibpathforVisualStudioCode.(Youwillneedtousethelocallyinstalledversionforcompilingaswell.)

Openingafolderasaworkspace

VisualStudioCodeisafile-andfolder-basededitor,whichmeansyoucanopenafileorafolderandstartwork.

ButyoustillneedtoproperlyconfiguretheprojecttotakethebestadvantageofCode.ForTypeScript,theprojectfileistsconfig.json,whichcontainsthedescriptionofsourcefilesandcompileroptions.Knowlittleabouttsconfig.json?Don'tworry,we'llcometothatlater.

HerearesomefeaturesofVisualStudioCodeyoumightbeinterestedin:

Tasks:Basictaskintegration.Youcanbuildyourprojectwithoutleavingtheeditor.Debugging:Node.jsdebuggingwithsourcemapsupport,whichmeansyoucandebugNode.jsapplicationswritteninTypeScript.Git:BasicGitintegration.Thismakescomparingandcommittingchangeseasier.

Configuringaminimumbuildtask

ThedefaultkeybindingforabuildtaskisCtrl+Shift+Borcmd+Shift+BonOSX.Bypressingthesekeys,youwillgetapromptnotifyingyouthatnotaskrunnerhasbeenconfigured.ClickConfigureTaskRunnerandthenselectaTypeScriptbuildtasktemplate(eitherwithorwithoutthewatchmodeenabled).Atasks.jsonfileunderthe.vscodefolderwillbecreatedautomaticallywithcontentsimilartothefollowing:

{

"version":"0.1.0",

"command":"tsc",

"isShellCommand":true,

"args":["-w","-p","."],

"showOutput":"silent",

"isWatching":true,

"problemMatcher":"$tsc-watch"

}

Page 445: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Nowcreateatest.tsfilewithsomehello-worldcodeandrunthebuildtaskagain.YoucaneitherpresstheshortcutwementionedbeforeorpressCtrl/Cmd+P,typetasktsc,andenter.

Ifyouweredoingthingscorrectly,youshouldbeseeingtheoutputtest.jsbythesideoftest.ts.

Therearesomeusefulconfigurationsfortaskingthatcan'tbecovered.YoumayfindmoreinformationonthewebsiteofVisualStudioCode:https://code.visualstudio.com/.

Frommyperspective,VisualStudioCodedeliversthebestTypeScriptdevelopmentexperienceintheclassofcodeeditors.Butifyouarenotafanofit,TypeScriptisalsoavailablewithofficialsupportforSublimeText.

Page 446: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SublimeTextwithTypeScriptpluginSublimeTextisanotherpopularlightweighteditoraroundthefieldwithamazingperformance.

ThefollowingimageshowshowTypeScriptIntelliSenseworksinSublimeText:

TheTypeScriptteamhasofficiallybuiltapluginforSublimeText(version3preferred),andyoucanfindadetailedintroduction,includingusefulshortcuts,intheirGitHubrepositoryhere:https://github.com/Microsoft/TypeScript-Sublime-Plugin.

Note

TherearestillsomeissueswiththeTypeScriptpluginforSublimeText.ItwouldbenicetoknowaboutthembeforeyoustartwritingTypeScriptwithSublimeText.

InstallingPackageControl

PackageControlisdefactopackagemanagerforSublimeText,withwhichwe'llinstalltheTypeScriptplugin.

Ifyoudon'thavePackageControlinstalled,performthefollowingsteps:

Page 447: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

1. ClickPreferences>BrowsePackages...,itopenstheSublimeTextpackagesfolder.2. BrowseuptotheparentfolderandthenintotheInstallPackagesfolder,anddownload

thefilebelowintothisfolder:https://packagecontrol.io/Package%20Control.sublime-package

3. RestartSublimeTextandyoushouldnowhaveaworkingpackagemanager.

NowweareonlyonestepawayfromIntelliSenseandrefactoringwithSublimeText.

InstallingtheTypeScriptplugin

WiththehelpofPackageControl,it'seasytoinstallaplugin:

1. OpentheSublimeTexteditor;pressCtrl+Shift+PforWindowsandLinuxorCmd+Shift+PforOSX.

2. TypeInstallPackageinthecommandpalette,selectPackageControl:InstallPackageandwaitforittoloadthepluginrepositories.

3. TypeTypeScriptandselecttoinstalltheofficialplugin.

NowwehaveTypeScriptreadyforSublimeText,cheers!

LikeVisualStudioCode,unmatchedTypeScriptversionsbetweenthepluginandcompilercouldleadtoproblems.Tofixthis,youcanaddthefield"typescript_tsdk"withapathtotheTypeScriptlibintheSettings-Userfile.

Page 448: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

OthereditororIDEoptionsVisualStudioCodeandSublimeTextarerecommendedduetotheireaseofuseandpopularityrespectively.Buttherearemanygreattoolsfromtheeditorclasstofull-featuredIDE.

Thoughwe'renotgoingthroughthesetupandconfigurationofthosetools,youmightwanttotrythemoutyourself,especiallyifyouarealreadyworkingwithsomeofthem.

However,theconfigurationfordifferenteditorsandIDEs(especiallyIDEs)coulddiffer.ItisrecommendedtouseVisualStudioCodeorSublimeTextforgoingthroughtheworkflowandexamplesinthisbook.

AtomwiththeTypeScriptplugin

Atomisacross-platformeditorcreatedbyGitHub.Ithasanotablecommunitywithplentyofusefulplugins,includingatom-typescript.atom-typescriptistheresultofthehardworkofBasaratAliSyed,andit'susedbymyteambeforeVisualStudioCode.IthasmanyhandyfeaturesthatVisualStudioCodedoesnothaveyet,suchasmodulepathsuggestion,compileonsave,andsoon.

LikeVisualStudioCode,Atomisalsoaneditorbasedonwebtechnologies.Actually,theshellusedbyVisualStudioCodeisexactlywhat'susedbyAtom:Electron,anotherpopularprojectbyGitHub,forbuildingcross-platformdesktopapplications.

Atomisproudofbeinghackable,whichmeansyoucancustomizeyourownAtomeditorprettymuchasyouwant.

ThenyoumaybewonderingwhyweturnedtoVisualStudioCode.ThemainreasonisthatVisualStudioCodeisbeingbackedbythesamecompanythatdevelopsTypeScript,andanotherreasonmightbetheperformanceissuewithAtom.

Butanyway,Atomcouldbeagreatchoiceforastart.

VisualStudio

VisualStudioisoneofthebestIDEsinthemarket.Andyetithas,ofcourse,officialTypeScriptsupport.

SinceVisualStudio2013,acommunityversionisprovidedforfreetoindividualdevelopers,smallcompanies,andopensourceprojects.

IfyouarelookingforapowerfulIDEofTypeScriptonWindows,VisualStudiocouldbeawonderfulchoice.ThoughVisualStudiohasbuilt-inTypeScriptsupport,domakesureit'sup-to-date.And,usually,youmightwanttoinstallthenewestTypeScripttoolsforVisualStudio.

Page 449: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WebStorm

WebStormisoneofthemostpopularIDEsforJavaScriptdevelopers,andithashadanearlyadoptiontoTypeScriptaswell.

AdownsideofusingWebStormforTypeScriptisthatitisalwaysonestepslowercatchinguptothelatestversioncomparedtoothermajoreditors.UnlikeeditorsthatdirectlyusethelanguageserviceprovidedbytheTypeScriptproject,WebStormseemstohaveitsowninfrastructureforIntelliSenseandrefactoring.But,inreturn,itmakesTypeScriptsupportinWebStormmorecustomizableandconsistentwithotherfeaturesitprovides.

IfyoudecidetouseWebStormasyourTypeScriptIDE,pleasemakesuretheversionofsupportedTypeScriptmatcheswhatyouexpect(usuallythelatestversion).

Page 450: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GettingyourhandsontheworkflowAftersettingupyoureditor,wearereadytomovetoaworkflowthatyoumightusetopracticethroughoutthisbook.ItcanalsobeusedastheworkflowforsmallTypeScriptprojectsinyourdailywork.

Inthisworkflow,we'llwalkthroughthesetopics:

Whatisatsconfig.jsonfile,andhowcanyouconfigureaTypeScriptprojectwithit?TypeScriptdeclarationfilesandthetypingscommand-linetoolHowtowritetestsrunningunderMocha,andhowtogetcoverageinformationusingIstanbulHowtotestinbrowsersusingKarma

Page 451: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringaTypeScriptprojectTheconfigurationsofaTypeScriptprojectcandifferforavarietyofreasons.Butthegoalsremainclear:weneedtheeditoraswellasthecompilertorecognizeaprojectanditssourcefilescorrectly.Andtsconfig.jsonwilldothejob.

Introductiontotsconfig.json

ATypeScriptprojectdoesnothavetocontainatsconfig.jsonfile.However,mosteditorsrelyonthisfiletorecognizeaTypeScriptprojectwithspecifiedconfigurationsandtoproviderelatedfeatures.

Atsconfig.jsonfileacceptsthreefields:compilerOptions,files,andexclude.Forexample,asimpletsconfig.jsonfilecouldbelikethefollowing:

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"rootDir":"src",

"outDir":"out"

},

"exclude":[

"out",

"node_modules"

]

}

Or,ifyouprefertomanagethesourcefilesmanually,itcouldbelikethis:

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"rootDir":"src",

"outDir":"out"

},

"files":[

"src/foo.ts",

"src/bar.ts"

]

}

Previously,whenweusedtsc,weneededtospecifythesourcefilesexplicitly.Now,withtsconfig.json,wecandirectlyruntscwithoutarguments(orwith-w/--watchifyouwantincrementalcompilation)inafolderthatcontainstsconfig.json.

Compileroptions

AsTypeScriptisstillevolving,itscompileroptionskeepchanging,withnewfeaturesandupdates.AninvalidoptionmaybreakthecompilationoreditorfeaturesforTypeScript.When

Page 452: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

readingtheseoptions,keepinmindthatsomeofthemmighthavebeenchanged.

Thefollowingoptionsareusefulonesoutofthelist.

target

targetspecifiestheexpectedversionofJavaScriptoutputs.Itcouldbees5(ECMAScript5),es6(ECMAScript6/2015),andsoon.

Features(especiallyECMAScriptpolyfills)thatareavailableindifferentcompilationtargetsvary.Forexample,beforeTypeScript2.1,featuressuchasasync/awaitwereavailableonlywhentargetingES6.

ThegoodnewsisthatNode.js6withthelatestV8enginehassupportedmostES6features.AndthelatestbrowsershavealsogreatES6support.SoifyouaredevelopingaNode.jsapplicationorabrowserapplicationthat'snotrequiredforbackwardcompatibilities,youcanhaveyourconfigurationtargetES6.

module

BeforeES6,JavaScripthadnostandardmodulesystem.Varietiesofmoduleloadersaredevelopedfordifferentscenarios,suchascommonjs,amd,umd,system,andsoon.

IfyouaredevelopingaNode.jsapplicationorannpmpackage,commonjscouldbethevalueofthisoption.Actually,withthehelpofmodernpackagingtoolssuchaswebpackandbrowserify,commonjscouldalsobeanicechoiceforbrowserprojectsaswell.

declaration

Enablethisoptiontogenerate.d.tsdeclarationfilesalongwithJavaScriptoutputs.Declarationfilescouldbeusefulasthetypeinformationsourceofadistributedlibrary/framework;itcouldalsobehelpfulforsplittingalargerprojecttoimprovecompilingperformanceanddivisioncooperation.

sourceMap

Byenablingthisoption,TypeScriptcompilerwillemitsourcemapsalongwithcompiledJavaScript.

jsx

TypeScriptprovidesbuilt-insupportforReactJSX(.tsx)files.Byspecifyingthisoptionwithvaluereact,TypeScriptcompilerwillcompile.tsxfilestoplainJavaScriptfiles.Orwithvaluepreserve,itwilloutput.jsxfilessoyoucanpost-processthesefileswithotherJSXcompilers.

noEmitOnError

Bydefault,TypeScriptwillemitoutputsnomatterwhethertypeerrorsarefoundornot.Ifthis

Page 453: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

isnotwhatyouwant,youmaysetthisoptiontotrue.

noEmitHelpers

WhencompilinganewerECMAScriptfeaturetoalowertargetversionofJavaScript,TypeScriptcompilerwillsometimesgeneratehelperfunctionssuchas__extends(ES6tolowerversions),and__awaiter(ES7tolowerversions).

Duetocertainreasons,youmaywanttowriteyourownhelperfunctions,andpreventTypeScriptcompilerfromemittingthesehelpers.

noImplicitAny

AsTypeScriptisasupersetofJavaScript,itallowsvariablesandparameterstohavenotypenotation.However,itcouldhelptomakesureeverythingistyped.

Byenablingthisoption,TypeScriptcompilerwillgiveerrorsifthetypeofavariable/parameterisnotspecifiedandcannotbeinferredbyitscontext.

experimentalDecorators*

Asdecorators,atthetimeofwritingthisbook,hasnotyetreachedastablestageofthenewECMAScriptstandard,youneedtoenablethisoptiontousedecorators.

emitDecoratorMetadata*

Runtimetypeinformationcouldsometimesbeuseful,butTypeScriptdoesnotyetsupportreflection(maybeitneverwill).Luckily,wegetdecoratormetadatathatwillhelpundercertainscenarios.

Byenablingthisoption,TypeScriptwillemitdecoratorsalongwithaReflect.metadata()decoratorwhichcontainsthetypeinformationofthedecoratedtarget.

outDir

Usually,wedonotwantcompiledfilestobeinthesamefolderofsourcecode.ByspecifyingoutDir,youcantellthecompilerwhereyouwouldwantthecompiledJavaScriptfilestobe.

outFile

Forsmallbrowserprojects,wemightwanttohavealltheoutputsconcatenatedasasinglefile.Byenablingthisoption,wecanachievethatwithoutextrabuildtools.

rootDir

TherootDiroptionistospecifytherootofthesourcecode.Ifomitted,thecompilerwouldusethelongestcommonpathofsourcefiles.Thismighttakesecondstounderstand.

Forexample,ifwehavetwosourcefiles,src/foo.tsandsrc/bar.ts,andatsconfig.json

Page 454: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

fileinthesamedirectoryofthesrcfolder,theTypeScriptcompilerwillusesrcastherootDir,sowhenemittingfilestotheoutDir(let'ssayout),theywillbeout/foo.jsandout/bar.js.

However,ifweaddanothersourcefiletest/test.tsandcompileagain,we'llgetthoseoutputslocatedinout/src/foo.js,out/src/bar.js,andout/test/test.jsrespectively.Whencalculatingthelongestcommonpath,declarationfilesarenotinvolvedastheyhavenooutput.

Usually,wedon'tneedtospecifyrootDir,butitwouldbesafertohaveitconfigured.

preserveConstEnums

EnumisausefultoolprovidedbyTypeScript.Whencompiled,it'sintheformofanEnum.memberexpression.Constantenum,ontheotherhand,emitsnumberliteralsdirectly,whichmeanstheEnumobjectisnolongernecessary.

AndthusTypeScript,bydefault,willremovethedefinitionsofconstantenumsinthecompiledJavaScriptfiles.

Byenablingthisoption,youcanforcethecompilertokeepthesedefinitionsanyway.

strictNullChecks

TypeScript2.1makesitpossibletoexplicitlydeclareatypewithundefinedornullasitssubtype.Andthecompilercannowperformmorethoroughtypecheckingforemptyvaluesifthisoptionisenabled.

stripInternal*

Whenemittingdeclarationfiles,therecouldbesomethingyou'llneedtouseinternallybutwithoutabetterwaytospecifytheaccessibility.Bycommentingthiscodewith/**@internal*/(JSDocannotation),TypeScriptcompilerthenwon'temitthemtodeclarationfiles.

isolatedModules

Byenablingthisoption,thecompilerwillunconditionallyemitimportsforunresolvedfiles.

Note

Optionssuffixedwith*areexperimentalandmighthavealreadybeenremovedwhenyouarereadingthisbook.Foramorecompleteandup-to-datecompileroptionslist,pleasecheckouthttp://www.typescriptlang.org/docs/handbook/compiler-options.html.

Addingsourcemapsupport

Sourcemapscanhelpalotwhiledebugging,nomatterforadebuggerorforerrorstack

Page 455: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

tracesfromalog.

Tohavesourcemapsupport,weneedtoenablethesourceMapcompileroptionintsconfig.json.Extraconfigurationsmightbenecessarytomakeyourdebuggerworkwithsourcemaps.

Forerrorstacktraces,wecanusethehelpofthesource-map-supportpackage:

$npminstallsource-map-support--save

Toputitintoeffect,youcanimportthispackagewithitsregistersubmoduleinyourentryfile:

import'source-map-support/register';

Page 456: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DownloadingdeclarationsusingtypingsJavaScripthasalargeandboomingecosystem.AsthebridgeconnectingTypeScriptandotherJavaScriptlibrariesandframeworks,declarationfilesareplayingaveryimportantrole.

Withthehelpofdeclarationfiles,TypeScriptdevelopercanuseexistingJavaScriptlibrarieswithnearlythesameexperienceaslibrarieswritteninTypeScript.

ThankstotheeffortsoftheTypeScriptcommunity,almosteverypopularJavaScriptlibraryorframeworkgotitsdeclarationfilesonaprojectcalledDefinitelyTyped.Andtherehasalreadybeenatoolcalledtsdfordeclarationfilemanagement.Butsoon,peoplerealizedthelimitationofasinglehugerepositoryforeverything,aswellastheissuestsdcannotsolvenicely.ThentypingsisgentlybecomingthenewtoolforTypeScriptdeclarationfilemanagement.

Installingtypings

typingsisjustanotherNode.jspackagewithacommand-lineinterfacelikeTypeScriptcompiler.Toinstalltypings,simplyexecutethefollowing:

$npminstalltypings-g

Tomakesureithasbeeninstalledcorrectly,youcannowtrythetypingscommandwithargument--version:

$typings--version

1.x.x

Downloadingdeclarationfiles

CreateabasicNode.jsprojectwithapropertsconfig.json(moduleoptionsetascommonjs),andatest.tsfile:

import*asexpressfrom'express';

Withoutthenecessarydeclarationfiles,thecompilerwouldcomplainwithCannotfindmoduleexpress.And,actually,youcan'tevenuseNode.jsAPIssuchasprocess.exit()orrequireNode.jsmodules,becauseTypeScriptitselfjustdoesnotknowwhatNode.jsis.

Tobeginwith,we'llneedtoinstalldeclarationfilesofNode.jsandExpress:

$typingsinstallenv~node--global

$typingsinstallexpress

Ifeverythinggoesfine,typingsshould'vedownloadedseveraldeclarationfilesandsavedthemtofoldertypings,includingnode.d.ts,express.d.ts,andsoon.AndIguessyou'vealreadynoticedthedependencyrelationshipexistingondeclarationfiles.

Page 457: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

IfthisisnotworkingforyouandtypingscomplainswithUnabletofind"express"("npm")intheregistrythenyoumightneedtodoitthehardway-tomanuallyinstallExpressdeclarationfilesandtheirdependenciesusingthefollowingcommand:$typingsinstalldt~<package-name>--global

ThereasonforthatisthecommunitymightstillbemovingfromDefinitelyTypedtothetypingsregistry.Theprefixdt~tellstypingstodownloaddeclarationfilesfromDefintelyTyped,and--globaloptiontellstypingstosavethesedeclarationfilesasambientmodules(namelydeclarationswithmodulenamespecified).

typingshasseveralregistries,andthedefaultoneiscallednpm(pleaseunderstandthisnpmregistryisnotthenpmpackageregistry).So,ifnoregistryisspecifiedwith<source>~prefixor--sourceoption,itwilltrytofinddeclarationfilesfromitsnpmregistry.Thismeansthattypingsinstallexpressisequivalenttotypingsinstallnpm~expressortypingsinstallexpress--sourcenpm.

Whiledeclarationfilesfornpmpackagesareusuallyavailableonthenpmregistry,declarationfilesfortheenvironmentareusuallyavailableontheenv.registry.Asthosedeclarationsareusuallyglobal,a--globaloptionisrequiredforthemtoinstallcorrectly.

Option"save"

typingsactuallyprovidesa--saveoptionforsavingthetypingnamesandfilesourcestotypings.json.However,inmyopinion,thisoptionisnotpracticallyuseful.

It'sgreattohavethemostpopularJavaScriptlibrariesandframeworkstyped,butthesedeclarationfiles,especiallydeclarationsnotfrequentlyused,canbeinaccurate,whichmeansthere'safairchancethatyouwillneedtoeditthesefilesyourself.

Itwouldbenicetocontributedeclarations,butitwouldalsobemoreflexibletohavetypingsmmanagedbysourcecontrolaspartoftheprojectcode.

Page 458: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingwithMochaandIstanbulTestingcouldbeanimportantpartofaproject,whichensuresfeatureconsistencyanddiscoversbugsearlier.Itiscommonthatachangemadeforonefeaturecouldbreakanotherworkingpartoftheproject.Arobustdesigncouldminimizethechancebutwestillneedteststomakesure.

Itcouldleadtoanendlessdiscussionabouthowtestsshouldbewrittenandthereareinterestingcodedesigntechniquessuchastest-drivendevelopment(TDD);thoughtherehasbeenalotofdebatesaroundit,itstillworthknowingandmayinspireyouincertainways.

MochaandChai

MochahasbeenoneofthemostpopulartestframeworksforJavaScript,whileChaiisagoodchoiceasanassertionlibrary.Tomakelifeeasier,youmaywritetestsforyourownimplementationsofcontentsthroughthisbookusingMochaandChai.

ToinstallMocha,simplyrunthefollowingcommand,anditwilladdmochaasaglobalcommand-linetooljustliketscandtypings:

$npminstallmocha-g

Chai,ontheotherhand,isusedasamoduleofaproject,andshouldbeinstalledundertheprojectfolderasadevelopmentdependency:

$npminstallchai--save-dev

Chaisupportsshouldstyleassertion.Byinvokingchai.should(),itaddstheshouldpropertytotheprototypeofObject,whichmeansyoucanthenwriteassertionssuchasthefollowing:

'foo'.should.not.equal('bar');

'typescript'.should.have.length(10);

WritingtestsinJavaScript

Byexecutingthecommandmocha,itwillautomaticallyruntestsinsidethetestfolder.BeforewestarttowritetestsinTypeScript,let'stryitoutinplainJavaScriptandmakesureit'sworking.

Createafiletest/starter.jsandsaveitwiththefollowingcode:

require('chai').should();

describe('somefeature',()=>{

it('shouldpass',()=>{

'foo'.should.not.equal('bar');

});

it('shoulderror',()=>{

(()=>{

Page 459: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thrownewError();

}).should.throw();

});

});

Runmochaundertheprojectfolderandyoushouldseealltestspassing.

WritingtestsinTypeScript

TestswritteninTypeScripthavetobecompiledbeforebeingrun;wheretoputthosefilescouldbeatrickyquestiontoanswer.

Somepeoplemightwanttoseparatetestswiththeirowntsconfig.json:

src/tsconfig.json

test/tsconfig.json

Theymayalsowanttoputoutputfilessomewherereasonable:

out/app/

out/test/

However,thiswillincreasethecostofbuildprocessmanagementforsmallprojects.So,ifyoudonotmindhavingsrcinthepathsofyourcompiledfiles,youcanhaveonlyonetsconfig.jsontogetthejobdone:

src/

test/

tsconfig.json

Thedestinationswillbeasfollows:

out/src/

out/test/

AnotheroptionIpersonallypreferistohavetestsinsideofsrc/test,andusethetestfolderundertheprojectrootforMochaconfigurations:

src/

src/test/

tsconfig.json

Thedestinationswillbeasfollows:

out/

out/test/

But,eitherway,we'llneedtoconfigureMochaproperlytodothefollowing:

Runtestsundertheout/testdirectoryConfiguretheassertionlibraryandothertoolsbeforestartingtoruntests

Page 460: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Toachievethese,wecantakeadvantageofthemocha.optsfileinsteadofspecifyingcommand-lineargumentseverytime.Mochawillcombinelinesinthemocha.optsfilewithothercommand-lineargumentsgivenwhilebeingloaded.

Createtest/mocha.optswiththefollowinglines:

--require./test/mocha.js

out/test/

Asyoumighthaveguessed,thefirstlineistotellMochatorequire./test/mocha.jsbeforestartingtorunactualtests.AndthesecondlinetellsMochawherethesetestsarelocated.

And,ofcourse,we'llneedtocreatetest/mocha.jscorrespondingly:

require('chai').should();

AlmostreadytowritetestsinTypeScript!ButTypeScriptcompilerdoesnotknowhowwouldfunctiondescribeoritbelike,soweneedtodownloaddeclarationfilesforMocha:

$typingsinstallenv~mocha--global

Nowwecanmigratethetest/starter.jsfiletosrc/test/starter.tswithnearlynochange,butremovingthefirstlinethatenablestheshouldstyleassertionofChai,aswehavealreadyputitintotest/mocha.js.

Compileandrun;buymeacupofcoffeeifitworks.Butitprobablywon't.We'vetalkedabouthowTypeScriptcompilerdeterminestherootofsourcefileswhenexplainingtherootDircompileroption.Aswedon'thaveanyTypeScriptfilesunderthesrcfolder(notincludingitssubfolders),TypeScriptcompilerusessrc/testastherootDir.Thusthecompiledtestfilesarenowundertheoutfolderinsteadoftheexpectedout/test.

Tofixthis,eitherexplicitlyspecifyrootDir,orjustaddthefirstnon-testTypeScriptfiletothesrcfolder.

GettingcoverageinformationwithIstanbul

Coveragecouldbeimportantformeasuringthequalityoftests.However,itmighttakemuchefforttoreachanumbercloseto100%,whichcouldbeaburdenfordevelopers.Tobalanceeffortsontestsandcodethatbringdirectvaluetotheproduct,therewouldgoanotherdebate.

InstallIstanbulvianpmjustaswiththeothertools:

$npminstallistanbul-g

ThesubcommandforIstanbultogeneratecodecoverageinformationisistanbulcover.ItshouldbefollowedbyaJavaScriptfile,butweneedtomakeitworkwithMocha,whichisacommand-linetool.Luckily,theentryoftheMochacommandis,ofcourse,aJavaScriptfile.

Page 461: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Tomakethemworktogether,we'llneedtoinstallalocal(insteadofglobal)versionofMochafortheproject:

$npminstallmocha--save-dev

Afterinstallation,we'llgetthefile_mochaundernode_modules/mocha/bin,whichistheJavaScriptentrywewerelookingfor.SonowwecanmakeIstanbulwork:

$istanbulcovernode_modules/mocha/bin/_mocha

Thenyoushould'vegotafoldernamedcoverage,andwithinitthecoveragereport.

Reviewingthecoveragereportisimportant;itcanhelpyoudecidewhetheryouneedtoaddtestsforspecificfeaturesandcodebranches.

Page 462: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestinginrealbrowserswithKarmaWe'vetalkedabouttestingwithMochaandIstanbulforNode.jsapplications.Itisanimportanttopicfortestingcodethatrunsinabrowseraswell.

KarmaisatestrunnerforJavaScriptthatmakestestinginrealbrowsersonrealdevicesmucheasier.ItofficiallysupportstheMocha,Jasmine,andJUnittestingframeworks,butit'salsopossibleforKarmatoworkwithanyframeworkviaasimpleadapter.

Creatingabrowserproject

ATypeScriptapplicationthatrunsinbrowserscanbequitedifferentfromaNode.jsone.Butifyouknowwhattheprojectshouldlooklikeafterthebuild,youshouldalreadyhavecluesonhowtoconfigurethatproject.

Toavoidintroducingtoomanyconceptsandtechnologiesnotdirectlyrelated,Iwillkeepthingsassimpleaspossible:

We'renotgoingtousemoduleloaderssuchasRequire.jsWe'renotgoingtotouchthecodepackagingprocess

Thismeanswe'llgowithseparatedoutputfilesthatneedtobeputintoanHTMLfilewithascripttagmanually.Here'sthetsconfig.jsonwe'llbeplayingwith;noticethatwenolongerhavethemoduleoption,set:

{

"compilerOptions":{

"target":"es5",

"rootDir":"src",

"outDir":"out"

},

"exclude":[

"out",

"node_modules"

]

}

Thenlet'screatepackage.jsonandinstallpackagesmochaandchaiwiththeirdeclarations:

$npminit

$npminstallmochachai--save-dev

$typingsinstallenv~mocha--global

$typingsinstallchai

Andtobeginwith,let'sfillthisprojectwithsomesourcecodeandtests.

Createsrc/index.tswiththefollowingcode:

functiongetLength(str:string):number{

returnstr.length;

Page 463: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

Andcreatesrc/test/test.tswithsometests:

describe('getlength',()=>{

it('"abc"shouldhavelength3',()=>{

getLength('abc').should.equal(3);

});

it('""shouldhavelength0',()=>{

getLength('').should.equal(0);

});

});

Again,inordertomaketheshouldstyleassertionwork,we'llneedtocallchai.should()beforetestsstart.Todoso,createfiletest/mocha.jsjustlikewedidintheNode.jsproject,thoughthecodelineslightlydiffers,aswenolongerusemodules:

chai.should();

Nowcompilethesefileswithtsc,andwe'vegotourprojectready.

InstallingKarma

KarmaitselfrunsonNode.js,andisavailableasannpmpackagejustlikeotherNode.jstoolswe'vebeenusing.ToinstallKarma,simplyexecutethenpminstallcommandintheprojectdirectory:

$npminstallkarma--save-dev

And,inourcase,wearegoingtohaveKarmaworkingwithMocha,Chai,andthebrowserChrome,sowe'llneedtoinstallrelatedplugins:

$npminstallkarma-mochakarma-chaikarma-chrome-launcher--save-dev

BeforeweconfigureKarma,itisrecommendedtohavekarma-cliinstalledgloballysothatwecanexecutethekarmacommandfromtheconsoledirectly:

$npminstallkarma-cli-g

ConfiguringandstartingKarma

TheconfigurationsaretotellKarmaaboutthetestingframeworksandbrowsersyouaregoingtouse,aswellasotherrelatedinformationsuchassourcefilesandteststorun.

TocreateaKarmaconfigurationfile,executekarmainitandansweritsquestions:

Testingframework:MochaRequire.js:noBrowsers:Chrome(addmoreifyoulike;besuretoinstallthecorrespondinglaunchers)Sourceandtestfiles:

Page 464: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

test/mocha.js(thefileenablesshouldstyleassertion)out/*.js(sourcefiles)out/test/*.js(testfiles)

Filestoexclude:emptyWatchforchanges:yes

Nowyoushouldseeakarma.conf.jsfileundertheprojectdirectory;openitwithyoureditorandadd'chai'tothelistofoptionframeworks.

Almostthere!Executethecommandkarmastartand,ifeverythinggoesfine,youshouldhavespecifiedbrowsersopenedwiththetestingresultsbeingloggedintheconsoleinseconds.

Page 465: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IntegratingcommandswithnpmThenpmprovidesasimplebutusefulwaytodefinecustomscriptsthatcanberunwiththenpmruncommand.Andithasanotheradvantage-whennpmrunacustomscript,itaddsnode_modules/.bintothePATH.Thismakesiteasiertomanageproject-relatedcommand-linetools.

Forexample,we'vetalkedaboutMochaandIstanbul.Theprerequisiteforhavingthemascommandsistohavetheminstalledglobally,whichrequiresextrastepsotherthannpminstall.Nowwecansimplysavethemasdevelopmentdependencies,andaddcustomscriptsinpackage.json:

"scripts":{

"test":"mocha",

"cover":"istanbulcovernode_modules/mocha/bin/_mocha"

},

"devDependencies":{

"mocha":"latest",

"istanbul":"latest"

}

Nowyoucanruntestwithnpmruntest(orsimplynpmtest),andruncoverwithnpmruncoverwithoutinstallingthesepackagesglobally.

Page 466: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Whynototherfancybuildtools?Youmightbewondering:whydon'tweuseabuildsystemsuchasGulptosetupourworkflow?Actually,whenIstartedtowritethischapter,IdidlistGulpasthetoolweweregoingtouse.Later,IrealizeditdoesnotmakemuchsensetouseGulptobuildtheimplementationsinmostofthechaptersinthisbook.

ThereisamessageIwanttodeliver:balance.

Once,Ihadadiscussiononbalanceversusprincipleswithmyboss.Thedisagreementwasclear:heinsistedoncontrollableprinciplesoversubjectivebalance,whileIprefercontextualbalanceoverfixedprinciples.

Actually,Iagreewithhim,fromthepointofviewofateamleader.Ateamisusuallybuiltupwithdevelopersatdifferentlevels;principlesmakeiteasierforateamtobuildhigh-qualityproducts,whilenoteveryoneisabletofindtherightbalancepoint.

However,whentheroleturnsfromaproductiveteammembertoalearner,itisimportanttolearnandtofeeltherightbalancepoint.Andthat'scalledexperience.

Page 467: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryThegoalofthischapterwastointroduceabasicworkflowthatcouldbeusedbythereadertoimplementthedesignpatternswe'llbediscussing.

WetalkedabouttheinstallationofTypeScriptcompilerthatrunsonNode.js,andhadbriefintroductionstopopularTypeScripteditorsandIDEs.Later,wespentquitealotofpageswalkingthroughthetoolsandframeworksthatcouldbeusedifthereaderwantstohavesomepracticewithimplementationsofthepatternsinthisbook.

Withthehelpofthesetoolsandframeworks,we'vebuiltaminimumworkflowthatincludescreating,building,andtestingaproject.Andtalkingaboutworkflows,youmusthavenoticedthattheyslightlydifferamongapplicationsfordifferentruntimes.

Inthenextchapter,we'lltalkaboutwhatmaygowrongandmessuptheentireprojectwhenitscomplexitykeepsgrowing.Andwe'lltrytocomeupwithspecificpatternsthatcansolvetheproblemsthisveryprojectfaces.

Page 468: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter2.TheChallengeofIncreasingComplexityTheessenceofaprogramisthecombinationofpossiblebranchesandautomatedselectionsbasedoncertainconditions.Whenwewriteaprogram,wedefinewhat'sgoingoninabranch,andunderwhatconditionthisbranchwillbeexecuted.

Thenumberofbranchesusuallygrowsquicklyduringtheevolutionofaproject,aswellasthenumberofconditionsthatdeterminewhetherabranchwillbeexecutedornot.

Thisisdangerousforhumanbeings,whohavelimitedbraincapacities.

Inthischapter,wearegoingtoimplementadatasynchronizingservice.Startingbyimplementingsomeverybasicfeatures,we'llkeepaddingstuffandseehowthingsgo.

Thefollowingtopicswillbecovered:

Designingamulti-devicesynchronizingstrategyUsefulJavaScriptandTypeScripttechniquesandhintsthatarerelated,includingobjectsasmapsandthestringliteraltypeHowtheStrategyPatternhelpsinaproject

Page 469: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthebasicsBeforewestarttowriteactualcode,weneedtodefinewhatthissynchronizingstrategywillbelike.Tokeeptheimplementationfromunnecessarydistractions,theclientwillcommunicatewiththeserverdirectlythroughfunctioncallsinsteadofusingHTTPrequestsorSockets.Also,we'llusein-memorystorage,namelyvariables,tostoredataonbothclientandserversides.

Becausewearenotseparatingtheclientandserverintotwoactualapplications,andwearenotactuallyusingbackendtechnologies,itdoesnotrequiremuchNode.jsexperiencetofollowthischapter.

However,pleasekeepinmindthateventhoughweareomittingnetworkanddatabaserequests,wehopethecorelogicofthefinalimplementationcouldbeappliedtoarealenvironmentwithoutbeingmodifiedtoomuch.So,whenitcomestoperformanceconcerns,westillneedtoassumelimitednetworkresources,especiallyfordatapassingthroughtheserverandclient,althoughtheimplementationisgoingtobesynchronousinsteadofasynchronous.Thisisnotsupposedtohappeninpractice,butinvolvingasynchronousoperationswillintroducemuchmorecode,aswellasmanymoresituationsthatneedtobetakenintoconsideration.Butwewillhavesomeusefulpatternsonasynchronousprogramminginthecomingchapters,anditwoulddefinitelyhelpifyoutrytoimplementanasynchronousversionofthesynchronizinglogicinthischapter.

Aclient,ifwithoutmodifyingwhat'sbeensynchronized,storesacopyofallthedataavailableontheserver,andwhatweneedtodoistoprovideasetofAPIsthatenabletheclienttokeepitscopyofdatasynchronized.

So,itisreallysimpleatthebeginning:comparingthelast-modifiedtimestamp.Ifthetimestampontheclientisolderthanwhat'sontheserver,thenupdatethecopyofdataalongwithnewtimestamp.

Page 470: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthecodebaseFirstly,let'screateserver.tsandclient.tsfilescontainingtheServerclassandClientclassrespectively:

exportclassServer{

//...

}

exportclassClient{

//...

}

Iprefertocreateanindex.tsfileasthepackageentry,whichhandleswhattoexportinternally.Inthiscase,let'sexporteverything:

export*from'./server';

export*from'./client';

ToimporttheServerandClientclassesfromatestfile(assumingsrc/test/test.ts),wecanusethefollowingcodetos:

import{Server,Client}from'../';

Page 471: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DefiningtheinitialstructureofthedatatobesynchronizedSinceweneedtocomparethetimestampsfromtheclientandserver,weneedtohaveatimestamppropertyonthedatastructure.Iwouldliketohavethedatatosynchronizeasastring,solet'saddaDataStoreinterfacewithatimestamppropertytotheserver.tsfile:

exportinterfaceDataStore{

timestamp:number;

data:string;

}

Page 472: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GettingdatabycomparingtimestampsCurrently,thesynchronizingstrategyisone-way,fromtheservertotheclient.Sowhatweneedtodoissimple:wecomparethetimestamps;iftheserverhasthenewerone,itrespondswithdataandtheserver-sidetimestamp;otherwise,itrespondswithundefined:

classServer{

store:DataStore={

timestamp:0,

data:''

};

getData(clientTimestamp:number):DataStore{

if(clientTimestamp<this.store.timestamp){

returnthis.store;

}else{

returnundefined;

}

}

}

NowwehaveprovidedasimpleAPIfortheclient,andit'stimetoimplementtheclient:

import{Server,DataStore}from'./';

exportclassClient{

store:DataStore={

timestamp:0,

data:undefined

};

constructor(

publicserver:Server

){}

}

Tip

Prefixingaconstructorparameterwithaccessmodifiers(includingpublic,private,andprotected)willcreateapropertywiththesamenameandcorrespondingaccessibility.Itwillalsoassignthevalueautomaticallywhentheconstructoriscalled.

NowweneedtoaddasynchronizemethodtotheClientclassthatdoesthejob:

synchronize():void{

letupdatedStore=this.server.getData(this.store.timestamp);

if(updatedStore){

this.store=updatedStore;

}

}

That'seasilydone.However,areyoualreadyfeelingsomewhatawkwardwithwhatwe've

Page 473: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

written?

Page 474: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Two-waysynchronizingUsually,whenwetalkaboutsynchronization,wegetupdatesfromtheserverandpushchangestotheserveraswell.Nowwearegoingtodothesecondpart,pushingthechangesiftheclienthasnewerdata.

Butfirst,weneedtogivetheclienttheabilitytoupdateitsdatabyaddinganupdatemethodtotheClientclass:

update(data:string):void{

this.store.data=data;

this.store.timestamp=Date.now();

}

Andweneedtheservertohavetheabilitytoreceivedatafromtheclientaswell.SowerenamethegetDatamethodoftheServerclassassynchronizeandmakeitsatisfythenewjob:

synchronize(clientDataStore:DataStore):DataStore{

if(clientDataStore.timestamp>this.store.timestamp){

this.store=clientDataStore;

returnundefined;

}elseif(clientDataStore.timestamp<this.store.timestamp){

returnthis.store;

}else{

returnundefined;

}

}

Nowwehavethebasicimplementationofoursynchronizingservice.Later,we'llkeepaddingnewthingsandmakeitcapableofdealingwithavarietyofscenarios.

Page 475: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThingsthatwentwrongwhileimplementingthebasicsCurrently,whatwe'vewrittenisjusttoosimpletobewrong.Buttherearestillsomesemanticissues.

Passingadatastorefromtheservertotheclientdoesnotmakesense

WeusedDataStoreasthereturntypeofthesynchronizemethodonServer.Butwhatwewereactuallypassingthroughisnotadatastore,butinformationthatinvolvesdataanditstimestamp.Theinformationobjectjusthappenedtohavethesamepropertiesasadatastoreatthispointintime.

Also,itwillbemisleadingtopeoplewhowilllaterreadyourcode(includingyourselfinthefuture).Mostofthetime,wearetryingtoeliminateredundancies.Butthatdoesnothavetomeaneverythingthatlooksthesame.Solet'smakeittwointerfaces:

interfaceDataStore{

timestamp:number;

data:string;

}

interfaceDataSyncingInfo{

timestamp:number;

data:string;

}

Iwouldevenprefertocreateanotherinstance,insteadofdirectlyreturningthis.store:

return{

timestamp:this.store.timestamp,

data:this.store.data

};

However,iftwopiecesofcodewithdifferentsemanticmeaningsaredoingthesamethingfromtheperspectiveofcodeitself,youmayconsiderextractingthatpartasautility.

Makingtherelationshipsclear

Nowwehavetwoseparatedinterfaces,DataStoreandDataSyncingInfo,inserver.ts.Obviously,DataSyncingInfoshouldbeasharedinterfacebetweentheserverandtheclient,whileDataStorehappenstobethesameonbothsides,butit'snotactuallyshared.

Sowhatwearegoingtodoistocreateaseparateshared.d.ts(itcouldalsobeshared.tsifitcontainsmorethantypings)thatexportsDataSyncingInfoandaddanotherDataStoretoclient.ts.

Note

Donotfollowthisblindly.Sometimesitisdesignedfortheserverandtheclienttohave

Page 476: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

exactlythesamestores.Ifthat'sthesituation,theinterfaceshouldbeshared.

Page 477: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GrowingfeaturesWhatwe'vedonesofarisbasicallyuseless.But,fromnowon,wewillstarttoaddfeaturesandmakeitcapableoffittinginpracticalneeds,includingthecapabilityofsynchronizingmultipledataitemswithmultipleclients,andmergingconflicts.

Page 478: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SynchronizingmultipleitemsIdeally,thedataweneedtosynchronizewillhavealotofitemscontained.Directlychangingthetypeofdatatoanarraywouldworkiftherewereonlyverylimitednumberoftheseitems.

Simplyreplacingdatatypewithanarray

Nowlet'schangethetypeofthedatapropertyofDataStoreandDataSyncingInfointerfacestostring[].WiththehelpofTypeScript,youwillgeterrorsforunmatchedtypesthischangewouldcause.Fixthembyannotatingthecorrecttypes.

Butobviously,thisisfarfromanefficientsolution.

Server-centeredsynchronization

Ifthedatastorecontainsalotofdata,theidealapproachwouldbeonlyupdatingitemsthatarenotup-to-date.

Forexample,wecancreateatimestampforeverysingleitemandsendthesetimestampstotheserver,thenlettheserverdecidewhetheraspecificdataitemisup-to-date.Thisisaviableapproachforcertainscenarios,suchascheckingupdatesforsoftwareextensions.ItisokaytooccasionallysendevenhundredsoftimestampswithitemIDsonafastnetwork,butwearegoingtouseanotherapproachfordifferentscenarios,orIwon'thavemuchtowrite.

Userdatasynchronizationofofflineappsonamobilephoneiswhatwearegoingtodealwith,whichmeansweneedtotryourbesttoavoidwastingnetworkresources.

Note

Hereisaninterestingquestion.Whatarethedifferencesbetweenuserdatasynchronizationandcheckingextensionupdates?Thinkaboutthesizeofdata,issueswithmultipledevices,andmore.

Thereasonwhywethoughtaboutsendingtimestampsofallitemsisfortheservertodeterminewhethercertainitemsneedtobeupdated.However,isitnecessarytohavethetimestampsofalldataitemsstoredontheclientside?

Whatifwechoosenottostorethetimestampofdatachanging,butofdatabeingsynchronizedwiththeserver?Thenwecangeteverythingup-to-datebyonlysendingthetimestampofthelastsuccessfulsynchronization.Theserverwillthencomparethistimestampwiththelastmodifiedtimestampsofalldataitemsanddecidehowtorespond.

Asthetitleofthispartsuggests,theprocessisserver-centeredandreliesontheservertogeneratethetimestamps(thoughitdoesnothaveto,andpracticallyshouldnot,bethestampoftheactualtime).

Page 479: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

Ifyouaregettingconfusedabouthowthesetimestampswork,let'stryagain.Theserverwillstorethetimestampsofthelasttimeitemsweresynchronized,andtheclientwillstorethetimestampofthelastsuccessfulsynchronizationwiththeserver.Thus,ifnoitemontheserverhasalatertimestampthantheclient,thenthere'snochangetotheserverdatastoreafterthattimestamp.Butiftherearesomechanges,bycomparingthetimestampoftheclientwiththetimestampsofserveritems,we'llknowwhichitemsarenewer.

Synchronizingfromtheservertotheclient

Nowthereseemstobequitealottochange.Firstly,let'shandlesynchronizingdatafromservertoclient.

Thisiswhat'sexpectedtohappenontheserverside:

AddatimestampandidentitytoeverydataitemontheserverComparetheclienttimestampwitheverydataitemontheserver

Note

Wedon'tneedtoactuallycomparetheclienttimestampwitheveryitemonserverifthoseitemshaveasortedindex.Theperformancewouldbeacceptableusingadatabasewithasortedindex.

Respondwithitemsnewerthanwhattheclienthasaswellasanewtimestamp.

Andhere'swhat'sexpectedtohappenontheclientside:

SynchronizewiththelasttimestampsenttotheserverUpdatethelocalstorewithnewdatarespondedbytheserverUpdatethelocaltimestampofthelastsynchronizationifitcompleteswithouterror

Updatinginterfaces

Firstofall,wehavenowanupdateddatastoreonbothsides.Startingwiththeserver,thedatastorenowcontainsanarrayofdataitems.Solet'sdefinetheServerDataIteminterfaceandupdateServerDataStoreaswell:

exportinterfaceServerDataItem{

id:string;

timestamp:number;

value:string;

}

exportinterfaceServerDataStore{

items:{

[id:string]:ServerDataItem;

};

}

Page 480: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

The{[id:string]:ServerDataItem}typedescribesanobjectwithidoftypestringasakeyandhasthevalueoftypeServerDataItem.Thus,anitemoftypeServerDataItemcanbeaccessedbyitems['the-id'].

Andfortheclient,wenowhavedifferentdataitemsandadifferentstore.Theresponsecontainsonlyasubsetofalldataitems,soweneedIDsandamapwithIDastheindextostorethedata:

exportinterfaceClientDataItem{

id:string;

value:string;

}

exportinterfaceClientDataStore{

timestamp:number;

items:{

[id:string]:ClientDataItem;

};

}

Previously,theclientandserverweresharingthesameDataSyncingInfo,butthat'sgoingtochange.Aswe'lldealwithserver-to-clientsynchronizingfirst,wecareonlyaboutthetimestampinasynchronizingrequestfornow:

exportinterfaceSyncingRequest{

timestamp:number;

}

Asfortheresponsefromtheserver,itisexpectedtohaveanupdatedtimestampwithdataitemsthathavechangedcomparedtotherequesttimestamp:

exportinterfaceSyncingResponse{

timestamp:number;

changes:{

[id:string]:string;

};

}

IprefixedthoseinterfaceswithServerandClientforbetterdifferentiation.Butit'snotnecessaryifyouarenotexportingeverythingfromserver.tsandclient.ts(inindex.ts).

Updatingtheserverside

Withwell-defineddatastructures,itshouldbeprettyeasytoachievewhatweexpected.Tobeginwith,wehavethesynchronizemethod,whichacceptsaSyncingRequestandreturnsaSyncingResponse;andweneedtohavetheupdatedtimestampaswell:

synchronize(request:SyncingRequest):SyncingResponse{

letlastTimestamp=request.timestamp;

Page 481: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

letnow=Date.now();

letserverChanges:ServerChangeMap=Object.create(null);

return{

timestamp:now,

changes:serverChanges

};

}

Tip

FortheserverChangesobject,{}(anobjectliteral)mightbethefirstthing(ifnotanES6Map)thatcomestomind.Butit'snotabsolutelysafetodoso,becauseitwouldrefuse__proto__asakey.ThebetterchoicewouldbeObject.create(null),whichacceptsallstringsasitskey.

NowwearegoingtoadditemsthatarenewerthantheclienttoserverChanges:

letitems=this.store.items;

for(letidofObject.keys(items)){

letitem=items[id];

if(item.timestamp>lastTimestamp){

serverChanges[id]=item.value;

}

}

Updatingtheclientside

Aswe'vechangedthetypeofitemsunderClientDataStoretoamap,weneedtofixtheinitialvalue:

store:ClientDataStore={

timestamp:0,

items:Object.create(null)

};

Nowlet'supdatethesynchronizemethod.Firstly,theclientisgoingtosendarequestwithatimestampandgetaresponsefromtheserver:

synchronize():void{

letstore=this.store;

letresponse=this.server.synchronize({

timestamp:store.timestamp

});

}

Thenwe'llsavethenewerdataitemstothestore:

letclientItems=store.items;

Page 482: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

letserverChanges=response.changes;

for(letidofObject.keys(serverChanges)){

clientItems[id]={

id,

value:serverChanges[id]

};

}

Finally,updatethetimestampofthelastsuccessfulsynchronization:

clientStore.timestamp=response.timestamp;

Note

Updatingthesynchronizationtimestampshouldbethelastthingtododuringacompletesynchronizationprocess.Makesureit'snotstoredearlierthandataitems,oryoumighthaveabrokenofflinecopyifthere'sanyerrorsorinterruptionsduringsynchronizinginthefuture.

Note

Toensurethatthisworksasexpected,anoperationwiththesamechangeinformationshouldgivethesameresultsevenifit'sappliedmultipletimes.

Synchronizingfromclienttoserver

Foraserver-centeredsynchronizingprocess,mostofthechangesaremadethroughclients.Consequently,weneedtofigureouthowtoorganizethesechangesbeforesendingthemtotheserver.

Onesingleclientonlycaresaboutitsowncopyofdata.Whatdifferencewouldthismakewhencomparingtotheprocessofsynchronizingdatafromtheservertoclients?Well,thinkaboutwhyweneedthetimestampofeverydataitemontheserverinthefirstplace.Weneedthembecausewewanttoknowwhichitemsarenewcomparedtoaspecificclient.

Now,forchangesonaclient:iftheyeverhappen,theyneedtobesynchronizedtotheserverwithoutrequiringspecifictimestampsforcomparison.

However,wemighthavemorethanoneclientwithchangesthatneedtobesynchronized,whichmeansthatchangesmadelaterintimemightactuallygetsynchronizedearlier,andthuswe'llhavetoresolveconflicts.Toachievethat,weneedtoaddthelastmodifiedtimebacktoeverydataitemontheserverandthechangeditemsontheclient.

I'vementionedthatthetimestampsstoredontheserverforfindingoutwhatneedstobesynchronizedtoaclientdonotneedtobe(andbetternotbe)anactualstampofaphysicaltimepoint.Forexample,itcouldbethecountofsynchronizationsthathappenedbetweenallclientsandtheserver.

Updatingtheclientside

Page 483: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Tohandlethisefficiently,wemaycreateaseparatedmapwiththeIDsofthedataitemsthathavechangedaskeysandthelastmodifiedtimeasthevalueinClientDataStore:

exportinterfaceClientDataStore{

timestamp:number;

items:{

[id:string]:ClientDataItem;

};

changed:{

[id:string]:number;

};

}

YoumayalsowanttoinitializeitsvalueasObject.create(null).

Nowwhenweupdateanitemintheclientstore,weaddthelastmodifiedtimetothechangedmapaswell:

update(id:string,value:string):void{

letstore=this.store;

store.items[id]={

id,

value

};

store.changed[id]=Date.now();

}

AsingletimestampinSyncingRequestcertainlywon'tdothejobanymore;weneedtoaddaplaceforthechangeddata,amapwithdataitemIDastheindex,andthechangedinformationasthevalue:

exportinterfaceClientChange{

lastModifiedTime:number;

value:string;

}

exportinterfaceSyncingRequest{

timestamp:number;

changes:{

[id:string]:ClientChange;

};

}

Herecomesanotherproblem.Whatifachangemadetoaclientdataitemisdoneoffline,withthesystemclockbeingatthewrongtime?Obviously,weneedsometimecalibrationmechanisms.However,there'snowaytomakeperfectcalibration.We'llmakesomeassumptionssowedon'tneedtostartanotherchapterfortimecalibration:

Thesystemclockofaclientmaybelateorearlycomparedtotheserver.Butitticksatanormalspeedandwon'tjumpbetweentimes.Therequestsentfromaclientreachestheserverinarelativelyshorttime.

Page 484: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Withthoseassumptions,wecanaddthosebuildingblockstotheclient-sidesynchronizemethod:

1. Addclient-sidechangestothesynchronizingrequest(ofcourse,beforesendingittotheserver):

letclientItems=store.items;

letclientChanges:ClientChangeMap=Object.create(null);

letchangedTimes=store.changed;

for(letidofObject.keys(changedTimes)){

clientChanges[id]={

lastModifiedTime:changedTimes[id],

value:clientItems[id].value

};

}

2. Synchronizechangestotheserverwiththecurrenttimeoftheclient'sclock:

letresponse=this.server.synchronize({

timestamp:store.timestamp,

clientTime:Date.now(),

changes:clientChanges

});

3. Cleanthechangesafterasuccessfulsynchronization:

store.changed=Object.create(null);

Updatingtheserverside

Iftheclientisworkingasexpected,itshouldsendsynchronizingrequestswithchanges.It'stimetoenabletheservertohandlingthosechangesfromtheclient.

Therearegoingtobetwostepsfortheserver-sidesynchronizationprocess:

1. Applytheclientchangestoserverdatastore.2. Preparethechangesthatneedtobesynchronizedtotheclient.

First,weneedtoaddlastModifiedTimetoserver-sidedataitems,aswementionedbefore:

exportinterfaceServerDataItem{

id:string;

timestamp:number;

lastModifiedTime:number;

value:string;

}

Andweneedtoupdatethesynchronizemethod:

letclientChanges=request.changes;

Page 485: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

letnow=Date.now();

for(letidofObject.keys(clientChanges)){

letclientChange=clientChanges[id];

if(

hasOwnProperty.call(items,id)&&

items[id].lastModifiedTime>clientChange.lastModifiedTime

){

continue;

}

items[id]={

id,

timestamp:now,

lastModifiedTime,

value:clientChange.value

};

}

Note

WecanactuallyusetheinoperatorinsteadofhasOwnPropertyherebecausetheitemsobjectiscreatedwithnullasitsprototype.ButareferencetohasOwnPropertywillbeyourfriendifyouareusingobjectscreatedbyobjectliterals,orinotherways,suchasmaps.

Wealreadytalkedaboutresolvingconflictsbycomparingthelastmodifiedtimes.Atthesametime,we'vemadeassumptionssowecancalibratethelastmodifiedtimesfromtheclienteasilybypassingtheclienttimetotheserverwhilesynchronizing.

Whatwearegoingtodoforcalibrationistocalculatetheoffsetoftheclienttimecomparedtotheservertime.Andthat'swhywemadethesecondassumption:therequestneedstoeasilyreachtheserverinarelativelyshorttime.Tocalculatetheoffset,wecansimplysubtracttheclienttimefromtheservertime:

letclientTimeOffset=now-request.clientTime;

Note

Tomakethetimecalibrationmoreaccurate,wewouldwanttheearliesttimestampaftertherequesthitstheservertoberecordedas"now".Soinpractice,youmightwanttorecordthetimestampoftherequesthittingtheserverbeforestartprocessingeverything.Forexample,forHTTPrequest,youmayrecordthetimestamponcetheTCPconnectiongetsestablished.

Andnow,thecalibratedtimeofaclientchangeisthesumoftheoriginaltimeandtheoffset.Wecannowdecidewhethertokeeporignoreachangefromtheclientbycomparingthecalibratedlastmodifiedtime.Itispossibleforthecalibratedtimetobegreaterthantheservertime;youcanchooseeithertousetheservertimeasthemaximumvalueoracceptasmallinaccuracy.Here,wewillgothesimpleway:

letlastModifiedTime=Math.min(

Page 486: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

clientChange.lastModifiedTime+clientTimeOffset,

now

);

if(

hasOwnProperty.call(items,id)&&

items[id].lastModifiedTime>lastModifiedTime

){

continue;

}

Tomakethisactuallywork,weneedtoalsoexcludechangesfromtheserverthatconflictwithclientchangesinSyncingResponse.Todoso,weneedtoknowwhatthechangesarethatsurvivetheconflictresolvingprocess.Asimplewayistoexcludeitemswithtimestampthatequalsnow:

for(letidofObject.keys(items)){

letitem=items[id];

if(

item.timestamp>lastTimestamp&&

item.timestamp!==now

){

serverChanges[id]=item.value;

}

}

Sonowwehaveimplementedacompletesynchronizationlogicwiththeabilitytohandlesimpleconflictsinpractice.

Page 487: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SynchronizingmultipletypesofdataAtthispoint,we'vehardcodedthedatawiththestringtype.Butusuallywewillneedtostorevarietiesofdata,suchasnumbers,booleans,objects,andsoon.

IfwewerewritingJavaScript,wewouldnotactuallyneedtochangeanything,astheimplementationdoesnothaveanythingtodowithcertaindatatypes.InTypeScript,wedon'tneedtodomucheither:justchangethetypeofeveryrelatedvaluetoany.Butthatmeansyouarelosingtypesafety,whichwoulddefinitelybeokayifyouarehappywiththat.

Buttakingmyownpreferences,Iwouldlikeeveryvariable,parameter,andpropertytobetypedifit'spossible.Sowemaystillhaveadataitemwithvalueoftypeany:

exportinterfaceClientDataItem{

id:string;

value:any;

}

Wecanalsohavederivedinterfacesforspecificdatatypes:

exportinterfaceClientStringDataItemextendsClientDataItem{

value:string;

}

exportinterfaceClientNumberDataItemextendsClientDataItem{

value:number;

}

Butthisdoesnotseemtobegoodenough.Fortunately,TypeScriptprovidesgenerics,sowecanrewritetheprecedingcodeasfollows:

exportinterfaceClientDataItem<T>{

id:string;

value:T;

}

Assumingwehaveastorethatacceptsmultipletypesofdataitems-forexample,numberandstring-wecandeclareitasfollowswiththehelpoftheuniontype:

exportinterfaceClientDataStore{

items:{

[id:string]:ClientDataItem<number|string>;

};

}

Ifyourememberthatwearedoingsomethingforofflinemobileapps,youmightbequestioningthelongpropertynamesinchangessuchaslastModifiedTime.Thisisafairquestion,andaneasyfixistousetupletypes,maybealongwithenums:

constenumClientChangeIndex{

lastModifiedType,

Page 488: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

value

}

typeClientChange<T>=[number,T];

letchange:ClientChange<string>=[0,'foo'];

letvalue=change[ClientChangeIndex.value];

Youcanapplylessormoreofthetypingthingswearetalkingaboutdependingonyourpreferences.Ifyouarenotfamiliarwiththemyet,youcanreadmorehere:http://www.typescriptlang.org/handbook.

Page 489: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SupportingmultipleclientswithincrementaldataMakingthetypingsystemhappywithmultipledatatypesiseasy.Butintherealworld,wedon'tresolveconflictsofalldatatypesbysimplycomparingthelastmodifiedtimes.Anexampleiscountingthedailyactivetimeofausercrossdevices.

It'squiteclearthatweneedtohaveeverypieceofactivetimeinadayonmultipledevicessummedup.Andthisishowwearegoingtoachievethat:

1. Accumulateactivedurationsbetweensynchronizationsontheclient.2. AddaUID(uniqueidentifier)toeverypieceoftimebeforesynchronizingwiththe

server.3. Increasetheserver-sidevalueiftheUIDdoesnotexistyet,andthenaddtheUIDtothat

dataitem.

Butbeforeweactuallygetourhandsonthosesteps,weneedawaytodistinguishincrementaldataitemsfromnormalones,forexample,byaddingatypeproperty.

Asoursynchronizingstrategyisserver-centered,relatedinformationisonlyrequiredforsynchronizingrequestsandconflictmerging.Synchronizingresponsesdoesnotneedtoincludethedetailsofchanges,butjustmergedvalues.

Note

Iwillstoptellinghowtoupdateeveryinterfacestepbystepasweareapproachingthefinalstructure.Butifyouhaveanyproblemswiththat,youcancheckoutthecompletecodebundleforinspiration.

Updatingtheclientside

Firstofall,weneedtheclienttosupportincrementalchanges.Andifyou'vethoughtaboutthis,youmightalreadybeconfusedaboutwheretoputtheextrainformation,suchasUIDs.

Thisisbecauseweweremixinguptheconceptchange(noun)withvalue.Itwasnotaproblembeforebecause,besidesthelastmodifiedtime,thevalueiswhatachangeisabout.Weusedasimplemaptostorethelastmodifiedtimesandkeptthestorecleanfromredundancy,whichbalancedwellunderthatscenario.

Butnowweneedtodistinguishbetweenthesetwoconcepts:

Value:avaluedescribeswhatadataitemisinastaticwayChange:achangedescribestheinformationthatmaytransformthevalueofadataitemfromonetoanother

Weneedtohaveageneraltypeofchangesaswellasanewdatastructureforincrementalchangeswithanumericvalue:

Page 490: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

typeDataType='value'|'increment';

interfaceClientChange{

type:DataType;

}

interfaceClientValueChange<T>extendsClientChange{

type:'value';

lastModifiedTime:number;

value:T;

}

interfaceClientIncrementChangeextendsClientChange{

type:'increment';

uid:string;

increment:number;

}

Note

Weareusingthestringliteraltypehere,whichwasintroducedinTypeScript1.8.Tolearnmore,pleaserefertotheTypeScripthandbookaswementionedbefore.

Similarchangestothedatastorestructureshouldbemade.Andwhenweupdateanitemontheclientside,weneedtoapplythecorrectoperationsbasedondifferentdatatypes:

update(id:string,type:'increment',increment:number):void;

update<T>(id:string,type:'value',value:T):void;

update<T>(id:string,type:DataType,value:T):void;

update<T>(id:string,type:DataType,value:T):void{

letstore=this.store;

letitems=store.items;

letstoredChanges=store.changes;

if(type==='value'){

//...

}elseif(type==='increment'){

//...

}else{

thrownewTypeError('Invaliddatatype');

}

}

Usethefollowingcodefornormalchanges(whiletypeequals'value'):

letchange:ClientValueChange<T>={

type:'value',

lastModifiedTime:Date.now(),

value

};

storedChanges[id]=change;

if(hasOwnProperty.call(items,id)){

Page 491: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

items[id].value=value;

}else{

items[id]={

id,

type,

value

};

}

Forincrementalchanges,ittakesafewmorelines:

letstoredChange=storedChanges[id]asClientIncrementChange;

if(storedChange){

storedChange.increment+=<any>valueasnumber;

}else{

storedChange={

type:'increment',

uid:Date.now().toString(),

increment:<any>valueasnumber

};

storedChanges[id]=storedChange;

}

Note

It'smypersonalpreferencetouse<T>foranycastingandasTfornon-anycastings.ThoughithasbeenusedinlanguageslikeC#,theasoperatorinTypeScriptwasoriginallyintroducedforcompatibilitieswithXMLtagsinJSX.Youcanalsowrite<number><any>valueorvalueasanyasnumberhereifyoulike.

Don'tforgettoupdatethestoredvalue.Justchange=to+=comparingtoupdatingnormaldataitems:

if(hasOwnProperty.call(items,id)){

items[id].value+=value;

}else{

items[id]={

id,

type,

value

};

}

That'snothardatall.Buthey,weseebranches.

Wearewritingbranchesallthetime,butwhatarethedifferencesbetweenbranchessuchasif(type==='foo'){...}andbranchessuchasif(item.timestamp>lastTimestamp){...}?Let'skeepthisquestioninmindandmoveon.

Withnecessaryinformationaddedbytheupdatemethod,wecannowupdatethesynchronize

Page 492: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

methodoftheclient.Butthereisaflawinpracticalscenarios:asynchronizingrequestissenttotheserversuccessfully,buttheclientfailedtoreceivetheresponsefromtheserver.Inthissituation,whenupdateiscalledafterafailedsynchronization,theincrementisaddedtothemight-be-synchronizedchange(identifiedbyitsUID),whichwillbeignoredbytheserverinfuturesynchronizations.Tofixthis,we'llneedtoaddamarktoallincrementalchangesthathavestartedasynchronizingprocess,andavoidaccumulatingthesechanges.Thus,weneedtocreateanotherchangeforthesamedataitem.

Thisisactuallyanicehint:asachangeisaboutinformationthattransformsavaluefromonetoanother,severalchangespendingsynchronizationmighteventuallybeappliedtoonesingledataitem:

interfaceClientChangeList<TextendsClientChange>{

type:DataType;

changes:T[];

}

interfaceSyncingRequest{

timestamp:number;

changeLists:{

[id:string]:ClientChangeList<ClientChange>;

};

}

interfaceClientIncrementChangeextendsClientChange{

type:'increment';

synced:boolean;

uid:string;

increment:number;

}

Nowwhenwearetryingtoupdateanincrementaldataitem,weneedtogetitslastchangefromthechangelist(ifany)andseewhetherithaseverbeensynchronized.Ifithaseverbeeninvolvedinasynchronization,wecreateanewchangeinstance.Otherwise,we'lljustaccumulatetheincrementpropertyvalueofthelastchangeontheclientside:

letchangeList=storedChangeLists[id];

letchanges=changeList.changes;

letlastChange=

changes[changes.length-1]asClientIncrementChange;

if(lastChange.synced){

changes.push({

synced:false,

uid:Date.now().toString(),

increment:<any>valueasnumber

}asClientIncrementChange);

}else{

lastChange.increment+=<any>valueasnumber;

}

Or,ifthechangelistdoesnotexistyet,we'llneedtosetitup:

Page 493: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

letchangeList={

type:'increment',

changes:[

{

synced:false,

uid:Date.now().toString(),

increment:<any>valueasnumber

}asClientIncrementChange

]

};

store.changeLists[id]=changeList;

Wealsoneedtoupdatesynchronizemethodtomarkanincrementalchangeassyncedbeforestartingthesynchronizationwiththeserver.Buttheimplementationisforyoutodoonyourown.

Updatingserverside

Beforeweaddthelogicforhandlingincrementalchanges,weneedtomakeserver-sidecodeadapttothenewdatastructure:

for(letidofObject.keys(clientChangeLists)){

letclientChangeList=clientChangeLists[id];

lettype=clientChangeList.type;

letclientChanges=clientChangeList.changes;

if(type==='value'){

//...

}elseif(type==='increment'){

//...

}else{

thrownewTypeError('Invaliddatatype');

}

}

Thechangelistofanormaldataitemwillalwayscontainoneandonlyonechange.Thuswecaneasilymigratewhatwe'vewritten:

letclientChange=changes[0]asClientValueChange<any>;

Nowforincrementalchanges,weneedtocumulativelyapplypossiblymultiplechangesinasinglechangelisttoadataitem:

letitem=items[id];

for(

letclientChange

ofclientChangesasClientIncrementChange[]

){

let{

uid,

increment

Page 494: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}=clientChange;

if(item.uids.indexOf(uid)<0){

item.value+=increment;

item.uids.push(uid);

}

}

ButremembertotakecareofthetimestamporcasesinwhichnoitemwithaspecifiedIDexists:

letitem:ServerDataItem<any>;

if(hasOwnProperty.call(items,id)){

item=items[id];

item.timestamp=now;

}else{

item=items[id]={

id,

type,

timestamp:now,

uids:[],

value:0

};

}

Withoutknowingthecurrentvalueofanincrementaldataitemontheclient,wecannotassurethatthevalueisuptodate.Previously,wedecidedwhethertorespondwithanewvaluebycomparingthetimestampwiththetimestampofthecurrentsynchronization,butthatdoesnotworkanymoreforincrementalchanges.

AsimplewaytomakethisworkisbydeletingkeysfromclientChangeListsthatstillneedtobesynchronizedtotheclient.Andwhenpreparingresponses,itcanskipIDsthatarestillinclientChangeLists:

if(

item.timestamp>lastTimestamp&&

!hasOwnProperty.call(clientChangeLists,id)

){

serverChanges[id]=item.value;

}

RemembertoadddeleteclientChangeLists[id];fornormaldataitemsthatdidnotsurviveconflictsresolvingaswell.

Nowwehaveimplementedasynchronizinglogicthatcandoquitealotjobsforofflineapplications.Earlier,Iraisedaquestionaboutincreasingbranchesthatdonotlookgood.Butifyouknowyourfeaturesaregoingtoendthere,oratleastwithlimitedchanges,it'snotabadimplementation,althoughwe'llsooncrossthebalancepoint,asmeeting80%oftheneedswon'tmakeushappyenough.

Page 495: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SupportingmoreconflictmergingThoughwehavemettheneedsof80%,thereisstillabigchancethatwemightwantsomeextrafeatures.Forexample,wewanttheratioofthedaysmarkedasavailablebytheuserinthecurrentmonth,andtheusershouldbeabletoaddorremovedaysfromthelist.Wecanachievethatindifferentways,andwe'llchooseasimpleway,asusual.

Wearegoingtosupportsynchronizingasetwithoperationssuchasaddandremove,andcalculatetheratioontheclient.

Newdatastructures

Todescribesetchanges,weneedanewClientChangetype.Whenweareaddingorremovinganelementfromaset,weonlycareaboutthelastoperationtothesameelement.Thismeansthatthefollowing:

1. Ifmultipleoperationsaremadetothesameelement,weonlyneedtokeepthelastone.2. Atimepropertyisrequiredforresolvingconflicts.

Soherearethenewtypes:

enumSetOperation{

add,

remove

}

interfaceClientSetChangeextendsClientChange{

element:number;

time:number;

operation:SetOperation;

}

Thesetdatastoredontheserversideisgoingtobealittledifferent.We'llhaveamapwiththeelement(intheformofastring)askey,andastructurewithoperationandtimepropertiesasthevalues:

interfaceServerSetElementOperationInfo{

operation:SetOperation;

time:number;

}

Nowwehaveenoughinformationtoresolveconflictsfrommultipleclients.Andwecangeneratethesetbykeyswithalittlehelpfromthelastoperationsdonetotheelements.

Updatingclientside

Andnow,theclient-sideupdatemethodgetsanewpart-timejob:savingsetchangesjustlikevalueandincrementalchanges.Weneedtoupdatethemethodsignatureforthisnewjob(donotforgettoadd'set'toDataType):

Page 496: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

update(

id:string,

type:'set',

element:number,

operation:SetOperation

):void;

update<T>(

id:string,

type:DataType,

value:T,

operation?:SetOperation

):void;

Wealsoneedtoaddanotherelseif:

elseif(type==='set'){

letelement=<any>valueasnumber;

if(hasOwnProperty.call(storedChangeLists,id)){

//...

}else{

//...

}

}

Iftherearealreadyoperationsmadetothisset,weneedtofindandremovethatlastoperationtothetargetelement(ifany).Thenappendanewchangewiththelatestoperation:

letchangeList=storedChangeLists[id];

letchanges=changeList.changesasClientSetChange[];

for(leti=0;i<changes.length;i++){

letchange=changes[i];

if(change.element===element){

changes.splice(i,1);

break;

}

}

changes.push({

element,

time:Date.now(),

operation

});

Ifnochangehasbeenmadesincelastsuccessfulsynchronization,we'llneedtocreateanewchangelistforthelatestoperation:

letchangeList:ClientChangeList<ClientSetChange>={

type:'set',

changes:[

{

element,

time:Date.now(),

Page 497: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

operation

}

]

};

storedChangeLists[id]=changeList;

Andagain,donotforgettoupdatethestoredvalue.Thisisalittlebitmorethanjustassigningoraccumulatingthevalue,butitshouldstillbequiteeasytoimplement.

Updatingtheserverside

Justlikewe'vedonewiththeclient,weneedtoaddacorrespondingelseifbranchtomergechangesoftype'set'.WearealsodeletingtheIDfromclientChangeListsregardlessofwhethertherearenewerchangesforasimplerimplementation:

elseif(type==='set'){

letitem:ServerDataItem<{

[element:string]:ServerSetElementOperationInfo;

}>;

deleteclientChangeLists[id];

}

Theconflictresolvinglogicisquitesimilartowhatwedototheconflictsofnormalvalues.Wejustneedtomakecomparisonstoeachelement,andonlykeepthelastoperation.

Andwhenpreparingtheresponsethatwillbesynchronizedtotheclient,wecangeneratethesetbyputtingtogetherelementswithaddastheirlastoperations:

if(item.type==='set'){

letoperationInfos:{

[element:string]:ServerSetElementOperationInfo;

}=item.value;

serverChanges[id]=Object

.keys(operationInfos)

.filter(element=>

operationInfos[element].operation===

SetOperation.add

)

.map(element=>Number(element));

}else{

serverChanges[id]=item.value;

}

Finally,wehaveaworkingmess(ifitactuallyworks).Cheers!

Page 498: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThingsthatgowrongwhileimplementingeverythingWhenwestartedtoaddfeatures,thingswereactuallyfine,ifyouarenotobsessiveaboutpursuingthefeelingofdesign.Thenwesensedthecodebeingalittleawkwardaswesawmoreandmorenestedbranches.

Sonowit'stimetoanswerthequestion,whatarethedifferencesbetweenthetwokindsofbranchwewrote?MyunderstandingofwhyIamfeelingawkwardabouttheif(type==='foo'){...}branchisthatit'snotstronglyrelatedtothecontext.Comparingtimestamps,ontheotherhand,isamorenaturalpartofacertainsynchronizingprocess.

Again,Iamnotsayingthisisbad.Butthisgivesusahintaboutwherewemightstartoursurgeryfromwhenwestarttolosecontrol(duetoourlimitedbraincapacity,it'sjustamatterofcomplexity).

Pilingupsimilaryetparallelprocesses

Mostofthecodeinthischapteristohandletheprocessofsynchronizingdatabetweenaclientandaserver.Togetadaptedtonewfeatures,wejustkeptaddingnewthingsintomethods,suchasupdateandsynchronize.

Youmighthavealreadyfoundthatmostoutlinesofthelogiccanbe,andshouldbe,sharedacrossmultipledatatypes.Butwedidn'tdothat.

Ifwelookintowhat'swritten,theduplicationisactuallyminorjudgingfromtheaspectofcodetexts.Takingtheupdatemethodoftheclient,forexample,thelogicofeverybranchseemstodiffer.Iffindingabstractionshasnotbecomeyourbuilt-inreaction,youmightjuststopthere.Orifyouarenotafanoflongfunctions,youmightrefactorthecodebysplittingitintosmallonesofthesameclass.Thatcouldmakethingsalittlebetter,butfarfromenough.

Datastoresthataretremendouslysimplified

Intheimplementation,wewereplayingheavilyanddirectlywithidealin-memorystores.Itwouldbeniceifwecouldhaveawrapperforit,andmaketherealstoreinterchangeable.

Thismightnotbethecaseforthisimplementationasitisbasedonextremelyidealandsimplifiedassumptionsandrequirements.Butaddingawrappercouldbeawaytoprovideusefulhelpers.

Page 499: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GettingthingsrightSolet'sgetoutoftheillusionofcomparingcodeonecharacteratatimeandtrytofindanabstractionthatcanbeappliedtoupdatingallofthesedatatypes.Therearetwokeypointsofthisabstractionthathavealreadybeenmentionedintheprevioussection:

AchangecontainstheinformationthatcantransformthevalueofanitemfromonetoanotherMultiplechangescouldbegeneratedorappliedtoonedataitemduringasinglesynchronization

Now,startingfromchanges,let'sthinkaboutwhathappenswhenanupdatemethodofaclientiscalled.

Page 500: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FindingabstractionTakeacloserlooktothemethodupdateofclient:

Fordataofthe'value'type,firstwecreatethechange,includinganewvalue,andthenupdatethechangelisttomakethenewlycreatedchangetheonlyone.Afterthat,weupdatethevalueofdataitem.Fordataofthe'increment'type,weaddachangeincludingtheincrementinthechangelist;orifachangethathasnotbesynchronizedalreadyexists,updatetheincrementoftheexistingchange.Andthen,weupdatethevalueofthedataitem.Finally,fordataofthe'set'type,wecreateachangereflectingthelatestoperation.Afteraddingthenewchangetothechangelist,wealsoremovechangesthatarenolongernecessary.Thenweupdatethevalueofthedataitem.

Thingsaregettingclear.Hereiswhat'shappeningtothesedatatypeswhenupdateiscalled:

1. Createnewchange.2. Mergethenewchangetothechangelist.3. Applythenewchangetothedataitem.

Nowit'sevenbetter.Everystepisdifferentfordifferentdatatypes,butdifferentstepssharethesameoutline;whatweneedtodoistoimplementdifferentstrategiesfordifferentdatatypes.

Page 501: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingstrategiesDoingallkindofchangeswithasingleupdatefunctioncouldbeconfusing.Andbeforewemoveon,let'ssplititintothreedifferentmethods:updatefornormalvalues,increaseforincrementalvalues,andaddTo/removeFromforsets.

ThenwearegoingtocreateanewprivatemethodcalledapplyChange,whichwilltakethechangecreatedbyothermethodsandcontinuewithstep2andstep3.Itacceptsastrategyobjectwithtwomethods:appendandapply:

interfaceClientChangeStrategy<TextendsClientChange>{

append(list:ClientChangeList<T>,change:T):void;

apply(item:ClientDataItem<any>,change:T):void;

}

Foranormaldataitem,thestrategyobjectcouldbeasfollows:

letstrategy:ClientChangeStrategy<ClientValueChange<any>>={

append(list,change){

list.changes=[change];

},

apply(item,change){

item.value=change.value;

}

};

Andforincrementaldataitem,ittakesafewmorelines.First,theappendmethod:

letchanges=list.changes;

letlastChange=changes[changes.length];

if(!lastChange||lastChange.synced){

changes.push(change);

}else{

lastChange.increment+=change.increment;

}

Theappendmethodisfollowedbytheapplymethod:

if(item.value===undefined){

item.value=change.increment;

}else{

item.value+=change.increment;

}

NowintheapplyChangemethod,weneedtotakecareofthecreationofnon-existingitemsandchangelists,andinvokedifferentappendandapplymethodsbasedondifferentdatatypes.

Thesametechniquecanbeappliedtootherprocesses.Thoughdetailedprocessesthatapplytotheclientandtheserverdiffer,wecanstillwritethemtogetherasmodules.

Page 502: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WrappingstoresWearegoingtomakealightweightwrapperaroundplainin-memorystoreobjectswiththeabilitytoreadandwrite,takingtheserver-sidestoreasanexample:

exportclassServerStore{

privateitems:{

[id:string]:ServerDataItem<any>;

}=Object.create(null);

}

exportclassServer{

constructor(

publicstore:ServerStore

){}

}

Tofitourrequirements,weneedtoimplementget,set,andgetAllmethods(orevenbetter,afindmethodwithconditions)forServerStore:

get<T,TExtraextendsServerDataItemExtra>(id:string):

ServerDataItem<T>&TExtra{

returnhasOwnProperty.call(this.items,id)?

this.items[id]asServerDataItem<T>&TExtra:undefined;

}

set<T,TExtraextendsServerDataItemExtra>(

id:string,

item:ServerDataItem<T>&Textra

):void{

this.items[id]=item;

}

getAll<T,TExtraextendsServerDataItemExtra>():

(ServerDataItem<T>&TExtra)[]{

letitems=this.items;

returnObject

.keys(items)

.map(id=>items[id]asServerDataItem<T>&TExtra);

}

YoumayhavenoticedfromtheinterfacesandgenericsthatI'vealsotorndownServerDataItemintointersectiontypesofthecommonpartandextras.

Page 503: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,we'vebeenpartoftheevolutionofasimplifiedyetreality-relatedproject.Startingwithasimplecodebasethatcouldn'tbewrong,weaddedalotoffeaturesandexperiencedtheprocessofputtingacceptablechangestogetherandmakingthewholethingamess.

Wewerealwaystryingtowritereadablecodebyeithernamingthingsnicelyoraddingsemanticallynecessaryredundancies,butthatwon'thelpmuchasthecomplexitygrows.

Duringtheprocess,we'velearnedhowofflinesynchronizingworks.Andwiththehelpofthemostcommondesignpatterns,suchastheStrategyPattern,wemanagedtosplittheprojectintosmallandcontrollableparts.

Intheupcomingchapters,we'llcatalogmoreusefuldesignpatternswithcodeexamplesinTypeScript,andtrytoapplythosedesignpatternstospecificissues.

Page 504: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter3.CreationalDesignPatternsCreationaldesignpatternsinobject-orientedprogrammingaredesignpatternsthataretobeappliedduringtheinstantiationofobjects.Inthischapter,we'llbetalkingaboutpatternsinthiscategory.

Considerwearebuildingarocket,whichhaspayloadandoneormorestages:

classPayload{

weight:number;

}

classEngine{

thrust:number;

}

classStage{

engines:Engine[];

}

Inold-fashionedJavaScript,therearetwomajorapproachestobuildingsucharocket:

ConstructorwithnewoperatorFactoryfunction

Forthefirstapproach,thingscouldbelikethis:

functionRocket(){

this.payload={

name:'cargoship'

};

this.stages=[

{

engines:[

//...

]

}

];

}

varrocket=newRocket();

Andforthesecondapproach,itcouldbelikethis:

functionbuildRocket(){

varrocket={};

rocket.payload={

name:'cargoship'

};

rocket.stages=[

Page 505: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

{

thrusters:[

//...

]

}

];

returnrocket;

}

varrocket=buildRocket();

Fromacertainangle,theyaredoingprettymuchthesamething,butsemanticallytheydifferalot.Theconstructorapproachsuggestsastrongassociationbetweenthebuildingprocessandthefinalproduct.Thefactoryfunction,ontheotherhand,impliesaninterfaceofitsproductandclaimstheabilitytobuildsuchaproduct.

However,neitheroftheprecedingimplementationsprovidestheflexibilitytomodularlyassemblerocketsbasedonspecificneeds;thisiswhatcreationaldesignpatternsareabout.

Inthischapter,we'llcoverthefollowingcreationalpatterns:

Factorymethod:Byusingabstractmethodsofafactoryinsteadoftheconstructortobuildinstances,thisallowssubclassestochangewhat'sbuiltbyimplementingoroverridingthesemethods.Abstractfactory:Definingtheinterfaceofcompatiblefactoriesandtheirproducts.Thusbychangingthefactorypassed,wecanchangethefamilyofbuiltproducts.Builder:Definingthestepsofbuildingcomplexobjects,andchangingwhat'sbuilteitherbychangingthesequenceofsteps,orusingadifferentbuilderimplementation.Prototype:Creatingobjectsbycloningparameterizedprototypes.Thusbyreplacingtheseprototypes,wemaybuilddifferentproducts.Singleton:Ensuringonlyoneinstance(underacertainscope)willbecreated.

ItisinterestingtoseethateventhoughthefactoryfunctionapproachtocreatingobjectsinJavaScriptlooksprimitive,itdoeshavepartsincommonwithsomepatternswearegoingtotalkabout(althoughappliedtodifferentscopes).

Page 506: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FactorymethodUndersomescenarios,aclasscannotpredictexactlywhatobjectsitwillcreate,oritssubclassesmaywanttocreatemorespecifiedversionsoftheseobjects.Then,theFactoryMethodPatterncanbeapplied.

ThefollowingpictureshowsthepossiblestructureoftheFactoryMethodPatternappliedtocreatingrockets:

Afactorymethodisamethodofafactorythatbuildsobjects.Takebuildingrocketsasanexample;afactorymethodcouldbeamethodthatbuildseithertheentirerocketorasinglecomponent.Onefactorymethodmightrelyonotherfactorymethodstobuilditstargetobject.Forexample,ifwehaveacreateRocketmethodundertheRocketclass,itwouldprobablycallfactorymethodslikecreateStagesandcreatePayloadtogetthenecessarycomponents.

TheFactoryMethodPatternprovidessomeflexibilityuponreasonablecomplexity.Itallowsextendableusagebyimplementing(oroverriding)specificfactorymethods.TakingcreateStagesmethod,forexample,wecancreateaone-stagerocketoratwo-stagerocketbyprovidingdifferentcreateStagesmethodthatreturnoneortwostagesrespectively.

Page 507: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofatypicalFactoryMethodPatternimplementationincludethefollowing:

Product:Rocket

Defineanabstractclassoraninterfaceofarocketthatwillbecreatedastheproduct.

Concreteproduct:FreightRocket

Implementaspecificrocketproduct.

Creator:RocketFactory

Definetheoptionallyabstractfactoryclassthatcreatesproducts.

Concretecreator:FreightRocketFactory

Implementoroverridesspecificfactorymethodstobuildproductsondemand.

Page 508: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeTheFactoryMethodPatterndecouplesRocketfromtheconstructorimplementationandmakesitpossibleforsubclassesofafactorytochangewhat'sbuiltaccordingly.Aconcretecreatorstillcaresaboutwhatexactlyitscomponentsareandhowtheyarebuilt.Buttheimplementationoroverridingusuallyfocusesmoreoneachcomponent,ratherthantheentireproduct.

Page 509: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationLet'sbeginwithbuildingasimpleone-stagerocketthatcarriesa0-weightpayloadasthedefaultimplementation:

classRocketFactory{

buildRocket():Rocket{}

createPayload():Payload{}

createStages():Stage[]{}

}

Westartwithcreatingcomponents.Wewillsimplyreturnapayloadwith0weightforthefactorymethodcreatePayloadandonesinglestagewithonesingleengineforthefactorymethodcreateStages:

createPayload():Payload{

returnnewPayload(0);

}

createStages():Stage[]{

letengine=newEngine(1000);

letstage=newStage([engine]);

return[stage];

}

Afterimplementingmethodstocreatethecomponentsofarocket,wearegoingtoputthemtogetherwiththefactorymethodbuildRocket:

buildRocket():Rocket{

letrocket=newRocket();

letpayload=this.createPayload();

letstages=this.createStages();

rocket.payload=payload;

rocket.stages=stages;

returnrocket;

}

Nowwehavetheblueprintofasimplerocketfactory,yetwithcertainextensibilities.Tobuildarocket(thatdoesnothingsofar),wejustneedtoinstantiatethisveryfactoryandcallitsbuildRocketmethod:

letrocketFactory=newRocketFactory();

letrocket=rocketFactory.buildRocket();

Next,wearegoingtobuildtwo-stagefreightrocketsthatsendsatellitesintoorbit.Thus,therearesomedifferencescomparedtothebasicfactoryimplementation.

Page 510: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

First,wehaveadifferentpayload,satellites,insteadofa0-weightplaceholder:

classSatelliteextendsPayload{

constructor(

publicid:number

){

super(200);

}

}

Second,wenowhavetwostages,probablywithdifferentspecifications.Thefirststageisgoingtohavefourengines:

classFirstStageextendsStage{

constructor(){

super([

newEngine(1000),

newEngine(1000),

newEngine(1000),

newEngine(1000)

]);

}

}

Whilethesecondstagehasonlyone:

classSecondStageextendsStage{

constructor(){

super([

newEngine(1000)

]);

}

}

Nowwehavewhatthisnewfreightrocketwouldlooklikeinmind,let'sextendthefactory:

typeFreightRocketStages=[FirstStage,SecondStage];

classFreightRocketFactoryextendsRocketFactory{

createPayload():Satellite{}

createStages():FreightRocketStages{}

}

Tip

Hereweareusingthetypealiasofatupletorepresentthestagessequenceofafreightrocket,namelythefirstandsecondstages.Tofindoutmoreabouttypealiases,pleaserefertohttps://www.typescriptlang.org/docs/handbook/advanced-types.html.

AsweaddedtheidpropertytoSatellite,wemightneedacounterforeachinstanceofthefactory,andthencreateeverysatellitewithauniqueID:

nextSatelliteId=0;

Page 511: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

createPayload():Satellite{

returnnewSatellite(this.nextSatelliteId++);

}

Let'smoveonandimplementthecreateStagesmethodthatbuildsfirstandsecondstageoftherocket:

createStages():FreightRocketStages{

return[

newFirstStage(),

newSecondStage()

];

}

Comparingtotheoriginalimplementation,youmayhavenoticedthatwe'veautomaticallydecoupledspecificstagebuildingprocessesfromassemblingthemintoconstructorsofdifferentstages.Itisalsopossibletoapplyanothercreationalpatternfortheinitiationofeverystageifithelps.

Page 512: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesIntheprecedingimplementation,thefactorymethodbuildRockethandlestheoutlineofthebuildingsteps.Wewereluckytohavethefreightrocketinthesamestructureastheveryfirstrocketwehaddefined.

Butthatwon'talwayshappen.Ifwewanttochangetheclassofproducts(Rocket),we'llhavetooverridetheentirebuildRocketwitheverythingelsebuttheclassname.Thislooksfrustratingbutitcanbesolved,again,bydecouplingthecreationofarocketinstancefromthebuildingprocess:

buildRocket():Rocket{

letrocket=this.createRocket();

letpayload=this.createPayload();

letstages=this.createStages();

rocket.payload=payload;

rocket.stages=stages;

returnrocket;

}

createRocket():Rocket{

returnnewRocket();

}

ThuswecanchangetherocketclassbyoverridingthecreateRocketmethod.However,thereturntypeofthebuildRocketofasubclass(forexample,FreightRocketFactory)isstillRocketinsteadofsomethinglikeFreightRocket.ButastheobjectcreatedisactuallyaninstanceofFreightRocket,itisvalidtocastthetypebytypeassertion:

letrocket=FreightRocketFactory.buildRocket()asFreightRocket;

Thetrade-offisalittletypesafety,butthatcanbeeliminatedusinggenerics.Unfortunately,inTypeScriptwhatyougetfromagenerictypeargumentisjustatypewithoutanactualvalue.Thismeansthatwemayneedanotherlevelofabstractionorotherpatternsthatcanusethehelpoftypeinferencetomakesureofeverything.

TheformeroptionwouldleadustotheAbstractFactoryPattern.

Note

Typesafetycouldbeonereasontoconsiderwhenchoosingapatternbutusually,itwillnotbedecisive.Pleasenotewearenottryingtoswitchapatternforthissinglereason,butjustexploring.

Page 513: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AbstractFactoryTheAbstractFactoryPatternusuallydefinestheinterfacesofacollectionoffactorymethods,withoutspecifyingconcreteproducts.Thisallowsanentirefactorytobereplaceable,inordertoproducedifferentproductsfollowingthesameproductionoutline:

Thedetailsoftheproducts(components)areomittedfromthediagram,butdonoticethattheseproductsbelongtotwoparallelfamilies:ExperimentalRocketandFreightRocket.

DifferentfromtheFactoryMethodPattern,theAbstractFactoryPatternextractsanotherpartcalledclientthattakecaresofshapingtheoutlineofthebuildingprocess.Thismakesthefactorypartfocusedmoreonproducingeachcomponent.

Page 514: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofatypicalAbstractFactoryPatternimplementationincludethefollowing:

Abstractfactory:RocketFactory

Definestheindustrialstandardsofafactorywhichprovideinterfacesformanufacturingcomponentsorcomplexproducts.

Concretefactory:ExperimentalRocketFactory,FreightRocketFactory

Implementstheinterfacesdefinedbytheabstractfactoryandbuildsconcreteproducts.

Abstractproducts:Rocket,Payload,Stage[]

Definetheinterfacesoftheproductsthefactoriesaregoingtobuild.

Concreteproducts:ExperimentalRocket/FreightRocket,ExperimentalPayload/Satellite,andsoon.

Presentsactualproductsthataremanufacturedbyaconcretefactory.

Client:

Arrangestheproductionprocessacrossfactories(onlyifthesefactoriesconformtoindustrialstandards).

Page 515: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeAbstractFactoryPatternmakestheabstractionontopofdifferentconcretefactories.Atthescopeofasinglefactoryorasinglebranchoffactories,itjustworksliketheFactoryMethodPattern.However,thehighlightofthispatternistomakeawholefamilyofproductsinterchangeable.AgoodexamplecouldbecomponentsofthemesforaUIimplementation.

Page 516: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationIntheAbstractFactoryPattern,itistheclientinteractingwithaconcretefactoryforbuildingintegralproducts.However,theconcreteclassofproductsisdecoupledfromtheclientduringdesigntime,whiletheclientcaresonlyaboutwhatafactoryanditsproductslooklikeinsteadofwhatexactlytheyare.

Let'sstartbysimplifyingrelatedclassestointerfaces:

interfacePayload{

weight:number;

}

interfaceStage{

engines:Engine[];

}

interfaceRocket{

payload:Payload;

stages:Stage[];

}

Andofcoursetheabstractfactoryitselfis:

interfaceRocketFactory{

createRocket():Rocket;

createPayload():Payload;

createStages():Stage[];

}

Thebuildingstepsareabstractedfromthefactoryandputintotheclient,butwestillneedtoimplementitanyway:

classClient{

buildRocket(factory:RocketFactory):Rocket{

letrocket=factory.createRocket();

rocket.payload=factory.createPayload();

rocket.stages=factory.createStages();

returnrocket;

}

}

NowwehavethesameissuewepreviouslyhadwhenweimplementedtheFactoryMethodPattern.Asdifferentconcretefactoriesbuilddifferentrockets,theclassoftheproductchanges.However,nowwehavegenericstotherescue.

First,weneedaRocketFactoryinterfacewithagenerictypeparameterthatdescribesaconcreterocketclass:

interfaceRocketFactory<TextendsRocket>{

Page 517: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

createRocket():T;

createPayload():Payload;

createStages():Stage[];

}

Andsecond,updatethebuildRocketmethodoftheclienttosupportgenericfactories:

buildRocket<TextendsRocket>(

factory:RocketFactory<T>

):T{}

Thus,withthehelpofthetypesystem,wewillhaverockettypeinferredbasedonthetypeofaconcretefactory,startingwithExperimentalRocketandExperimentalRocketFactory:

classExperimentalRocketimplementsRocket{}

classExperimentalRocketFactory

implementsRocketFactory<ExperimentalRocket>{}

IfwecallthebuildRocketmethodofaclientwithaninstanceofExperimentalRocketFactory,thereturntypewillautomaticallybeExperimentalRocket:

letclient=newClient();

letfactory=newExperimentalRocketFactory();

letrocket=client.buildRocket(factory);

BeforewecancompletetheimplementationoftheExperimentalRocketFactoryobject,weneedtodefineconcreteclassesfortheproductsofthefamily:

classExperimentalPayloadimplementsPayload{

weight:number;

}

classExperimentalRocketStageimplementsStage{

engines:Engine[];

}

classExperimentalRocketimplementsRocket{

payload:ExperimentalPayload;

stages:[ExperimentalRocketStage];

}

Note

Trivialinitializationsofpayloadandstageareomittedformorecompactcontent.Thesamekindsofomissionmaybeappliediftheyarenotnecessaryforthisbook.

Andnowwemaydefinethefactorymethodsofthisconcretefactoryclass:

classExperimentalRocketFactory

implementsRocketFactory<ExperimentalRocket>{

createRocket():ExperimentalRocket{

returnnewExperimentalRocket();

Page 518: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

createPayload():ExperimentalPayload{

returnnewExperimentalPayload();

}

createStages():[ExperimentalRocketStage]{

return[newExperimentalRocketStage()];

}

}

Let'smoveontoanotherconcretefactorythatbuildsafreightrocketandproductsofitsfamily,startingwiththerocketcomponents:

classSatelliteimplementsPayload{

constructor(

publicid:number,

publicweight:number

){}

}

classFreightRocketFirstStageimplementsStage{

engines:Engine[];

}

classFreightRocketSecondStageimplementsStage{

engines:Engine[];

}

typeFreightRocketStages=

[FreightRocketFirstStage,FreightRocketSecondStage];

Continuewiththerocketitself:

classFreightRocketimplementsRocket{

payload:Satellite;

stages:FreightRocketStages;

}

Withthestructuresorclassesofthefreightrocketfamilydefined,wearereadytoimplementitsfactory:

classFreightRocketFactory

implementsRocketFactory<FreightRocket>{

nextSatelliteId=0;

createRocket():FreightRocket{

returnnewFreightRocket();

}

createPayload():Satellite{

returnnewSatellite(this.nextSatelliteId++,100);

}

createStages():FreightRocketStages{

Page 519: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

return[

newFreightRocketFirstStage(),

newFreightRocketSecondStage()

];

}

}

Nowweonceagainhavetwofamiliesofrocketsandtheirfactories,andwecanusethesameclienttobuilddifferentrocketsbypassingdifferentfactories:

letclient=newClient();

letexperimentalRocketFactory=newExperimentalRocketFactory();

letfreightRocketFactory=newFreightRocketFactory();

letexperimentalRocket=

client.buildRocket(experimentalRocketFactory);

letfreightRocket=client.buildRocket(freightRocketFactory);

Page 520: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesTheAbstractFactoryPatternmakesiteasyandsmoothtochangetheentirefamilyofproducts.Thisisthedirectbenefitbroughtbythefactorylevelabstraction.Asaconsequence,italsobringsotherbenefits,aswellassomedisadvantagesatthesametime.

Ontheonehand,itprovidesbettercompatibilitywithintheproductsinaspecificfamily.Astheproductsbuiltbyasinglefactoryareusuallymeanttoworktogether,wecanassumethattheytendtocooperatemoreeasily.

Butontheotherhand,itreliesonacommonoutlineofthebuildingprocess,althoughforawell-abstractedbuildingprocess,thiswon'talwaysbeanissue.Wecanalsoparameterizefactorymethodsonbothconcretefactoriesandtheclienttomaketheprocessmoreflexible.

Ofcourse,anabstractfactorydoesnothavetobeapureinterfaceoranabstractclasswithnomethodsimplemented.Animplementationinpracticeshouldbedecidedbasedondetailedcontext.

AlthoughtheAbstractFactoryPatternandFactoryMethodPatternhaveabstractionsofdifferentlevels,whattheyencapsulatearesimilar.Forbuildingaproductwithmultiplecomponents,thefactoriessplittheproductsintocomponentstogainflexibility.However,afixedfamilyofproductsandtheirinternalcomponentsmaynotalwayssatisfytherequirements,andthuswemayconsidertheBuilderPatternasanotheroption.

Page 521: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BuilderWhileFactoryPatternsexposetheinternalcomponents(suchasthepayloadandstagesofarocket),theBuilderPatternencapsulatesthembyexposingonlythebuildingstepsandprovidesthefinalproductsdirectly.Atthesametime,theBuilderPatternalsoencapsulatestheinternalstructuresofaproduct.Thismakesitpossibleforamoreflexibleabstractionandimplementationofbuildingcomplexobjects.

TheBuilderPatternalsointroducesanewrolecalleddirector,asshowninthefollowingdiagram.ItisquiteliketheclientintheAbstractFactoryPattern,althoughitcaresonlyaboutbuildstepsorpipelines:

NowtheonlyconstraintfromRocketBuilderthatappliestoaproductofitssubclassistheoverallshapeofaRocket.ThismightnotbringalotofbenefitswiththeRocketinterfacewepreviouslydefined,whichexposessomedetailsoftherocketthattheclients(byclientsImeanthosewhowanttosendtheirsatellitesorotherkindsofpayloadtospace)maynotcareaboutthatmuch.Fortheseclients,whattheywanttoknowmightjustbewhichorbittherocketiscapableofsendingtheirpayloadsto,ratherthanhowmanyandwhatstagesthisrockethas.

Page 522: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofatypicalBuilderPatternimplementationincludethefollowing:

Builder:RocketBuilder

Definestheinterfaceofabuilderthatbuildsproducts.

Concretebuilder:FalconBuilder

Implementsmethodsthatbuildpartsoftheproducts,andkeepstrackofthecurrentbuildingstate.

Director

Definesthestepsandcollaborateswithbuilderstobuildproducts.

Finalproduct:Falcon

Theproductbuiltbyabuilder.

Page 523: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeTheBuilderPatternhasasimilarscopetotheAbstractFactoryPattern,whichextractsabstractionfromacompletecollectionofoperationsthatwillfinallyinitiatetheproducts.ComparedtotheAbstractFactoryPattern,abuilderintheBuilderPatternfocusesmoreonthebuildingstepsandtheassociationbetweenthosesteps,whiletheAbstractFactoryPatternputsthatpartintotheclientsandmakesitsfactoryfocusonproducingcomponents.

Page 524: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationAsnowweareassumingthatstagesarenottheconcernoftheclientswhowanttobuyrocketstocarrytheirpayloads,wecanremovethestagespropertyfromthegeneralRocketinterface:

interfaceRocket{

payload:Payload;

}

Thereisarocketfamilycalledsoundingrocketthatsendsprobestonearspace.Andthismeanswedon'tevenneedtohavetheconceptofstages.SoundingRocketisgoingtohaveonlyoneenginepropertyotherthanpayload(whichwillbeaProbe),andtheonlyenginewillbeaSolidRocketEngine:

classProbeimplementsPayload{

weight:number;

}

classSolidRocketEngineextendsEngine{}

classSoundingRocketimplementsRocket{

payload:Probe;

engine:SolidRocketEngine;

}

Butstillweneedrocketstosendsatellites,whichusuallyuseLiquidRocketEngine:

classLiquidRocketEngineextendsEngine{

fuelLevel=0;

refuel(level:number):void{

this.fuelLevel=level;

}

}

AndwemightwanttohavethecorrespondingLiquidRocketStageabstractclassthathandlesrefuelling:

abstractclassLiquidRocketStageimplementsStage{

engines:LiquidRocketEngine[]=[];

refuel(level=100):void{

for(letengineofthis.engines){

engine.refuel(level);

}

}

}

NowwecanupdateFreightRocketFirstStageandFreightRocketSecondStageassubclassesofLiquidRocketStage:

classFreightRocketFirstStageextendsLiquidRocketStage{

constructor(thrust:number){

Page 525: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

super();

letenginesNumber=4;

letsingleEngineThrust=thrust/enginesNumber;

for(leti=0;i<enginesNumber;i++){

letengine=

newLiquidRocketEngine(singleEngineThrust);

this.engines.push(engine);

}

}

}

classFreightRocketSecondStageextendsLiquidRocketStage{

constructor(thrust:number){

super();

this.engines.push(newLiquidRocketEngine(thrust));

}

}

TheFreightRocketwillremainthesameasitwas:

typeFreightRocketStages=

[FreightRocketFirstStage,FreightRocketSecondStage];

classFreightRocketimplementsRocket{

payload:Satellite;

stages=[]asFreightRocketStages;

}

And,ofcourse,thereisthebuilder.Thistime,wearegoingtouseanabstractclassthathasthebuilderpartiallyimplemented,withgenericsapplied:

abstractclassRocketBuilder<

TRocketextendsRocket,

TPayloadextendsPayload

>{

createRocket():void{}

addPayload(payload:TPayload):void{}

addStages():void{}

refuelRocket():void{}

abstractgetrocket():TRocket;

}

Note

There'sactuallynoabstractmethodinthisabstractclass.Oneofthereasonsisthatspecificstepsmightbeoptionaltocertainbuilders.Byimplementingno-opmethods,thesubclassescanjustleavethestepstheydon'tcareaboutempty.

Page 526: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

HereistheimplementationoftheDirectorclass:

classDirector{

prepareRocket<

TRocketextendsRocket,

TPayloadextendsPayload

>(

builder:RocketBuilder<TRocket,TPayload>,

payload:TPayload

):TRocket{

builder.createRocket();

builder.addPayload(payload);

builder.addStages();

builder.refuelRocket();

returnbuilder.rocket;

}

}

Note

Becautious,withoutexplicitlyprovidingabuildingcontext,thebuilderinstancereliesonthebuildingpipelinesbeingqueued(eithersynchronouslyorasynchronously).Onewaytoavoidrisk(especiallywithasynchronousoperations)istoinitializeabuilderinstanceeverytimeyoupreparearocket.

Nowit'stimetoimplementconcretebuilders,startingwithSoundingRocketBuilder,whichbuildsaSoundingRocketwithonlyoneSolidRocketEngine:

classSoundingRocketBuilder

extendsRocketBuilder<SoundingRocket,Probe>{

privatebuildingRocket:SoundingRocket;

createRocket():void{

this.buildingRocket=newSoundingRocket();

}

addPayload(probe:Probe):void{

this.buildingRocket.payload=probe;

}

addStages():void{

letpayload=this.buildingRocket.payload;

this.buildingRocket.engine=

newSolidRocketEngine(payload.weight);

}

getrocket():SoundingRocket{

returnthis.buildingRocket;

}

}

Thereareseveralnotablethingsinthisimplementation:

TheaddStagesmethodreliesonthepreviouslyaddedpayloadtoaddanenginewiththe

Page 527: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

correctthrustspecification.Therefuelmethodisnotoverridden(soitremainsno-op)becauseasolidrocketenginedoesnotneedtoberefueled.

We'vesensedalittleaboutthecontextprovidedbyabuilder,anditcouldhaveasignificantinfluenceontheresult.Forexample,let'stakealookatFreightRocketBuilder.ItcouldbesimilartoSoundingRocketifwedon'ttaketheaddStagesandrefuelmethodsintoconsideration:

classFreightRocketBuilder

extendsRocketBuilder<FreightRocket,Satellite>{

privatebuildingRocket:FreightRocket;

createRocket():void{

this.buildingRocket=newFreightRocket();

}

addPayload(satellite:Satellite):void{

this.buildingRocket.payload=satellite;

}

getrocket():FreightRocket{

returnthis.buildingRocket;

}

}

Assumethatapayloadthatweighslessthan1000takesonlyonestagetosendintospace,whilepayloadsweighingmoretaketwoormorestages:

addStages():void{

letrocket=this.buildingRocket;

letpayload=rocket.payload;

letstages=rocket.stages;

stages[0]=newFreightRocketFirstStage(payload.weight*4);

if(payload.weight>=FreightRocketBuilder.oneStageMax){

stages[1]=FreightRocketSecondStage(payload.weight);

}

}

staticoneStageMax=1000;

Whenitcomestorefueling,wecanevendecidehowmuchtorefuelbasedontheweightofthepayloads:

refuel():void{

letrocket=this.buildingRocket;

letpayload=rocket.payload;

letstages=rocket.stages;

letoneMax=FreightRocketBuilder.oneStageMax;

lettwoMax=FreightRocketBuilder.twoStagesMax;

Page 528: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

letweight=payload.weight;

stages[0].refuel(Math.min(weight,oneMax)/oneMax*100);

if(weight>=oneMax){

stages[1]

.refuel((weight-oneMax)/(twoMax-oneMax)*100);

}

}

staticoneStageMax=1000;

statictwoStagesMax=2000;

Nowwecanpreparedifferentrocketsreadytolaunch,withdifferentbuilders:

letdirector=newDirector();

letsoundingRocketBuilder=newSoundingRocketBuilder();

letprobe=newProbe();

letsoundingRocket

=director.prepareRocket(soundingRocketBuilder,probe);

letfreightRocketBuilder=newFreightRocketBuilder();

letsatellite=newSatellite(0,1200);

letfreightRocket

=director.prepareRocket(freightRocketBuilder,satellite);

Page 529: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesAstheBuilderPatterntakesgreatercontroloftheproductstructuresandhowthebuildingstepsinfluenceeachother,itprovidesthemaximumflexibilitybysubclassingthebuilderitself,withoutchangingthedirector(whichplaysasimilarroletoaclientintheAbstractFactoryPattern).

Page 530: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrototypeAsJavaScriptisaprototype-basedprogramminglanguage,youmightbeusingprototyperelatedpatternsallthetimewithoutknowingit.

We'vetalkedaboutanexampleintheAbstractFactoryPattern,andpartofthecodeislikethis:

classFreightRocketFactory

implementsRocketFactory<FreightRocket>{

createRocket():FreightRocket{

returnnewFreightRocket();

}

}

Sometimeswemayneedtoaddasubclassjustforchangingtheclassnamewhileperformingthesamenewoperation.Instancesofasingleclassusuallysharethesamemethodsandproperties,sowecancloneoneexistinginstancefornewinstancestobecreated.Thatistheconceptofaprototype.

ButinJavaScript,withtheprototypeconceptbuilt-in,newConstructor()doesbasicallywhataclonemethodwoulddo.Soactuallyaconstructorcanplaytheroleofaconcretefactoryinsomeway:

interfaceConstructor<T>{

new():T;

}

functioncreateFancyObject<T>(constructor:Constructor<T>):T{

returnnewconstructor();

}

Withthisprivilege,wecanparameterizeproductorcomponentclassesaspartofotherpatternsandmakecreationevenmoreflexible.

ThereissomethingthatcouldeasilybeignoredwhentalkingaboutthePrototypePatterninJavaScript:cloningwiththestate.WiththeclasssyntaxsugarintroducedinES6,whichhidestheprototypemodifications,wemayoccasionallyforgetthatwecanactuallymodifyprototypesdirectly:

classBase{

state:number;

}

letbase=newBase();

base.state=0;

classDerivedextendsBase{}

Derived.prototype=base;

letderived=newDerived();

Page 531: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Now,thederivedobjectwillkeepthestateofthebaseobject.Thiscouldbeusefulwhenyouwanttocreatecopiesofaspecificinstance,butkeepinmindthatpropertiesinaprototypeofthesecopiesarenottheownpropertiesoftheseclonedobjects.

Page 532: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SingletonTherearescenariosinwhichonlyoneinstanceofthespecificclassshouldeverexist,andthatleadstoSingletonPattern.

Page 533: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BasicimplementationsThesimplestsingletoninJavaScriptisanobjectliteral;itprovidesaquickandcheapwaytocreateauniqueobject:

constsingleton={

foo():void{

console.log('bar');

}

};

Butsometimeswemightwantprivatevariables:

constsingleton=(()=>{

letbar='bar';

return{

foo():void{

console.log(bar);

}

};

})();

OrwewanttotaketheadvantageofananonymousconstructorfunctionorclassexpressioninES6:

constsingleton=newclass{

private_bar='bar';

foo():void{

console.log(this._bar);

}

}();

Note

Rememberthattheprivatemodifieronlyhasaneffectatcompiletime,andsimplydisappearsafterbeingcompiledtoJavaScript(althoughofcourseitsaccessibilitywillbekeptin.d.ts).

However,itispossibletohavetherequirementsforcreatingnewinstancesof"singletons"sometimes.Thusanormalclasswillstillbehelpful:

classSingleton{

bar='bar';

foo():void{

console.log(bar);

}

privatestatic_default:Singleton;

staticgetdefault():Singleton{

Page 534: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

if(!Singleton._default){

Singleton._default=newSingleton();

}

returnSingleton._default;

}

}

Anotherbenefitbroughtbythisapproachislazyinitialization:theobjectonlygetsinitializedwhenitgetsaccessedthefirsttime.

Page 535: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConditionalsingletonsSometimeswemightwanttoget"singletons"basedoncertainconditions.Forexample,everycountryusuallyhasonlyonecapitalcity,thusacapitalcitycouldbetreatedasasingletonunderthescopeofthespecificcountry.

Theconditioncouldalsobetheresultofcontextratherthanexplicitarguments.AssumingwehaveaclassEnvironmentanditsderivedclasses,WindowsEnvironmentandUnixEnvironment,wewouldliketoaccessthecorrectenvironmentsingletonacrossplatformsbyusingEnvironment.defaultandapparently,aselectioncouldbemadebythedefaultgetter.

Formorecomplexscenarios,wemightwantaregistration-basedimplementationtomakeitextendable.

Page 536: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,we'vetalkedaboutseveralimportantcreationaldesignpatternsincludingtheFactoryMethod,AbstractFactory,Builder,Prototype,andSingleton.

StartingwiththeFactoryMethodPattern,whichprovidesflexibilitywithlimitedcomplexity,wealsoexploredtheAbstractFactoryPattern,theBuilderPatternandthePrototypePattern,whichsharesimilarlevelsofabstractionbutfocusondifferentaspects.ThesepatternshavemoreflexibilitythantheFactoryMethodPattern,butaremorecomplexatthesametime.Withtheknowledgeoftheideabehindeachofthepatterns,weshouldbeabletochooseandapplyapatternaccordingly.

Whilecomparingthedifferences,wealsofoundmanythingsincommonbetweendifferentcreationalpatterns.Thesepatternsareunlikelytobeisolatedfromothersandsomeofthemcanevencollaboratewithorcompleteeachother.

Inthenextchapter,we'llcontinuetodiscussstructuralpatternsthathelptoformlargeobjectswithcomplexstructures.

Page 537: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter4.StructuralDesignPatternsWhilecreationalpatternsplaythepartofflexiblycreatingobjects,structuralpatterns,ontheotherhand,arepatternsaboutcomposingobjects.Inthischapter,wearegoingtotalkaboutstructuralpatternsthatfitdifferentscenarios.

Ifwetakeacloserlookatstructuralpatterns,theycanbedividedintostructuralclasspatternsandstructuralobjectpatterns.Structuralclasspatternsarepatternsthatplaywith"interestedparties"themselves,whilestructuralobjectpatternsarepatternsthatweavepiecestogether(likeCompositePattern).Thesetwokindsofstructuralpatternscomplementeachothertosomedegree.

Herearethepatternswe'llwalkthroughinthischapter:

Composite:Buildstree-likestructuresusingprimitiveandcompositeobjects.AgoodexamplewouldbetheDOMtreethatformsacompletepage.Decorator:Addsfunctionalitytoclassesorobjectsdynamically.Adapter:Providesageneralinterfaceandworkwithdifferentadapteesbyimplementingdifferentconcreteadapters.Considerprovidingdifferentdatabasechoicesforasinglecontentmanagementsystem.Bridge:Decouplestheabstractionfromitsimplementation,andmakebothoftheminterchangeable.Façade:Providesasimplifiedinterfaceforthecombinationofcomplexsubsystems.Flyweight:Sharesstatelessobjectsthatarebeingusedmanytimestoimprovememoryefficiencyandperformance.Proxy:Actsasthesurrogatethattakesextraresponsibilitieswhenaccessingobjectsitmanages.

Page 538: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CompositePatternObjectsunderthesameclasscouldvaryfromtheirpropertiesorevenspecificsubclasses,butacomplexobjectcanhavemorethanjustnormalproperties.TakingDOMelements,forexample,alltheelementsareinstancesofclassNode.Thesenodesformtreestructurestorepresentdifferentpages,buteverynodeinthesetreesiscompleteanduniformcomparedtothenodeattheroot:

<html>

<head>

<title>TypeScript</title>

</head>

<body>

<h1>TypeScript</h1>

<img/>

</body>

</html>

TheprecedingHTMLrepresentsaDOMstructurelikethis:

AlloftheprecedingobjectsareinstancesofNode,theyimplementtheinterfaceofacomponentinCompositePattern.SomeofthesenodeslikeHTMLelements(exceptforHTMLImageElement)inthisexamplehavechildnodes(components)whileothersdon't.

Page 539: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofCompositePatternimplementationinclude:

Component:Node

Definestheinterfaceandimplementthedefaultbehaviorforobjectsofthecomposite.Itshouldalsoincludeaninterfacetoaccessandmanagethechildcomponentsofaninstance,andoptionallyareferencetoitsparent.Composite:IncludessomeHTMLelements,likeHTMLHeadElementandHTMLBodyElement

Storeschildcomponentsandimplementsrelatedoperations,andofcourseitsownbehaviors.Leaf:TextNode,HTMLImageElement

Definesbehaviorsofaprimitivecomponent.Client:

Manipulatesthecompositeanditscomponents.

Page 540: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeCompositePatternapplieswhenobjectscanandshouldbeabstractedrecursivelyascomponentsthatformtreestructures.Usually,itwouldbeanaturalchoicewhenacertainstructureneedstobeformedasatree,suchastreesofviewcomponents,abstractsyntaxtrees,ortreesthatrepresentfilestructures.

Page 541: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationWearegoingtocreateacompositethatrepresentssimplefilestructuresandhaslimitedkindsofcomponents.

Firstofall,let'simportrelatednodemodules:

import*asPathfrom'path';

import*asFSfrom'fs';

Note

Modulepathandfsarebuilt-inmodulesofNode.js,pleaserefertoNode.jsdocumentationformoreinformation:https://nodejs.org/api/.

Note

Itismypersonalpreferencetohavethefirstletterofanamespace(ifit'snotafunctionatthesametime)inuppercase,whichreducesthechanceofconflictswithlocalvariables.ButamorepopularnamingstylefornamespaceinJavaScriptdoesnot.

Nowweneedtomakeabstractionofthecomponents,sayFileSystemObject:

abstractclassFileSystemObject{

constructor(

publicpath:string,

publicparent?:FileSystemObject

){}

getbasename():string{

returnPath.basename(this.path);

}

}

WeareusingabstractclassbecausewearenotexpectingtouseFileSystemObjectdirectly.Anoptionalparentpropertyisdefinedtoallowustovisittheuppercomponentofaspecificobject.Andthebasenamepropertyisaddedasahelperforgettingthebasenameofthepath.

TheFileSystemObjectisexpectedtohavesubclasses,FolderObjectandFileObject.ForFolderObject,whichisacompositethatmaycontainotherfoldersandfiles,wearegoingtoaddanitemsproperty(getter)thatreturnsotherFileSystemObjectitcontains:

classFolderObjectextendsFileSystemObject{

items:FileSystemObject[];

constructor(path:string,parent?:FileSystemObject){

super(path,parent);

}

}

Page 542: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wecaninitializetheitemspropertyintheconstructorwithactualfilesandfoldersexistingatgivenpath:

this.items=FS

.readdirSync(this.path)

.map(path=>{

letstats=FS.statSync(path);

if(stats.isFile()){

returnnewFileObject(path,this);

}elseif(stats.isDirectory()){

returnnewFolderObject(path,this);

}else{

thrownewError('Notsupported');

}

});

Youmayhavenoticedweareformingitemswithdifferentkindsofobjects,andwearealsopassingthisastheparentofnewlycreatedchildcomponents.

AndforFileObject,we'lladdasimplereadAllmethodthatreadsallbytesofthefile:

classFileObjectextendsFileSystemObject{

readAll():Buffer{

returnFS.readFileSync(this.path);

}

}

Currently,wearereadingthechilditemsinsideafolderfromtheactualfilesystemwhenafolderobjectgetsinitiated.Thismightnotbenecessaryifwewanttoaccessthisstructureondemand.Wemayactuallycreateagetterthatcallsreaddironlywhenit'saccessed,thustheobjectwouldactlikeaproxytotherealfilesystem.

Page 543: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesBoththeprimitiveobjectandcompositeobjectinCompositePatternsharethecomponentinterface,whichmakesiteasyfordeveloperstobuildacompositestructurewithfewerthingstoremember.

ItalsoenablesthepossibilityofusingmarkuplanguageslikeXMLandHTMLtorepresentareallycomplexobjectwithextremeflexibility.CompositePatterncanalsomaketherenderingeasierbyhavingcomponentsrenderedrecursively.

Asmostcomponentsarecompatiblewithhavingchildcomponentsorbeingchildcomponentsoftheirparentsthemselves,wecaneasilycreatenewcomponentsthatworkgreatwithexistingones.

Page 544: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DecoratorPatternDecoratorPatternaddsnewfunctionalitytoanobjectdynamically,usuallywithoutcompromisingtheoriginalfeatures.TheworddecoratorinDecoratorPatterndoessharesomethingwiththeworddecoratorintheES-nextdecoratorsyntax,buttheyarenotexactlythesame.ClassicalDecoratorPatternasaphrasewoulddifferevenmore.

TheclassicalDecoratorPatternworkswithacomposite,andthebriefideaistocreatedecoratorsascomponentsthatdothedecoratingwork.Ascompositeobjectsareusuallyprocessedrecursively,thedecoratorcomponentswouldgetprocessedautomatically.Soitbecomesyourchoicetodecidewhatitdoes.

Theinheritancehierarchycouldbelikethefollowingstructureshown:

Thedecoratorsareappliedrecursivelylikethis:

Page 545: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Therearetwoprerequisitesforthedecoratorstoworkcorrectly:theawarenessofcontextorobjectthatadecoratorisdecorating,andtheabilityofthedecoratorsbeingapplied.TheCompositePatterncaneasilycreatestructuresthatsatisfythosetwoprerequisites:

ThedecoratorknowswhatitdecoratesasthecomponentpropertyThedecoratorgetsappliedwhenitisrenderedrecursively

However,itdoesn'treallyneedtotakeastructurelikeacompositetogainthebenefitsfromDecoratorPatterninJavaScript.AsJavaScriptisadynamiclanguage,ifyoucangetyourdecoratorscalled,youmayaddwhateveryouwanttoanobject.

Takingmethodlogunderconsoleobjectasanexample,ifwewantatimestampbeforeeverylog,wecansimplyreplacethelogfunctionwithawrapperthathasthetimestampprefixed:

const_log=console.log;

console.log=function(){

lettimestamp=`[${newDate().toTimeString()}]`;

return_log.apply(this,[timestamp,...arguments]);

};

Certainly,thisexamplehaslittletodowiththeclassicalDecoratorPattern,butitenablesadifferentwayforthispatterntobedoneinJavaScript.Especiallywiththehelpofnewdecoratorsyntax:

classTarget{

@decorator

method(){

//...

}

}

Note

TypeScriptprovidesthedecoratorsyntaxtransformationasanexperimentalfeature.Tolearnmoreaboutdecoratorsyntax,pleasecheckoutthefollowinglink:http://www.typescriptlang.org/docs/handbook/decorators.html.

Page 546: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofclassicalDecoratorPatternimplementationinclude:

Component:UIComponent

Definestheinterfaceoftheobjectsthatcanbedecorated.ConcreteComponent:TextComponent

Definesadditionalfunctionalitiesoftheconcretecomponent.Decorator:Decorator

Definesareferencetothecomponenttobedecorated,andmanagesthecontext.Conformstheinterfaceofacomponentwithproperbehaviors.ConcreteDecorator:ColorDecorator,FontDecorator

DefinesadditionalfeaturesandexposesAPIifnecessary.

Page 547: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeDecoratorPatternusuallycaresaboutobjects,butasJavaScriptisprototype-based,decoratorswouldworkwellwiththeclassesofobjectsthroughtheirprototypes.

TheclassicalimplementationofDecoratorPatterncouldhavemuchincommonwithotherpatternswearegoingtotalkaboutlater,whilethefunctiononeseemstoshareless.

Page 548: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationInthispart,we'lltalkabouttwoimplementationsofDecoratorPattern.ThefirstonewouldbeclassicalDecoratorPatternthatdecoratesthetargetbywrappingwithnewclassesthatconformtotheinterfaceofUIComponent.Thesecondonewouldbedecoratorswritteninnewdecoratorsyntaxthatprocessestargetobjects.

Classicaldecorators

Let'sgetstartedbydefiningtheoutlineofobjectstobedecorated.First,we'llhavetheUIComponentasanabstractclass,definingitsabstractfunctiondraw:

abstractclassUIComponent{

abstractdraw():void;

}

ThenaTextComponentthatextendstheUIComponent,aswellasitstextcontentsofclassText:

classText{

content:string;

setColor(color:string):void{}

setFont(font:string):void{}

draw():void{}

}

classTextComponentextendsUIComponent{

texts:Text[];

draw():void{

for(lettextofthis.texts){

text.draw();

}

}

}

What'snextistodefinetheinterfaceofdecoratorstodecorateobjectsthatareinstancesofclassTextComponent:

classDecoratorextendsUIComponent{

constructor(

publiccomponent:TextComponent

){

super();

}

gettexts():Text[]{

returnthis.component.texts;

}

draw():void{

this.component.draw();

Page 549: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

}

Nowwehaveeverythingforconcretedecorators.Inthisexample,ColorDecoratorandFontDecoratorlooksimilar:

classColorDecoratorextendsDecorator{

constructor(

component:TextComponent,

publiccolor:string

){

super(component);

}

draw():void{

for(lettextofthis.texts){

text.setColor(this.color);

}

super.draw();

}

}

classFontDecoratorextendsDecorator{

constructor(

component:TextComponent,

publicfont:string

){

super(component);

}

draw():void{

for(lettextofthis.texts){

text.setFont(this.font);

}

super.draw();

}

}

Note

Intheimplementationjustdescribed,this.textsindrawmethodcallsthegetterdefinedonclassDecorator.AsthisinthatcontextwouldideallybeaninstanceofclassColorDecoratororFontDecorator;thetextsitaccesseswouldfinallybethearrayinitscomponentproperty.

Note

Thiscouldbeevenmoreinterestingorconfusingifwehavenesteddecoratorslikewewillsoon.Trytodrawaschematicdiagramifitconfusesyoulater.

Nowit'stimetoactuallyassemblethem:

letdecoratedComponent=newColorDecorator(

Page 550: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

newFontDecorator(

newTextComponent(),

'sans-serif'

),

'black'

);

Theorderofnestingdecoratorsdoesnotmatterinthisexample.AseitherColorDecoratororFontDecoratorisavalidUIComponent,theycanbeeasilydroppedinandreplacepreviousTextComponent.

DecoratorswithES-nextsyntax

ThereisalimitationwithclassicalDecoratorPatternthatcanbepointedoutdirectlyviaitsnestingformofdecorating.ThatappliestoES-nextdecoratorsaswell.Takealookatthefollowingexample:

classFoo{

@prefix

@suffix

getContent():string{

return'...';

}

}

Tip

Whatfollowsthe@characterisanexpressionthatevaluatestoadecorator.Whileadecoratorisafunctionthatprocessestargetobjects,weusuallyusehigher-orderfunctionstoparameterizeadecorator.

WenowhavetwodecoratorsprefixandsuffixdecoratingthegetContentmethod.Itseemsthattheyarejustparallelatfirstglance,butifwearegoingtoaddaprefixandsuffixontothecontentreturned,likewhatthenamesuggests,theprocedurewouldactuallyberecursiveratherthanparalleljustliketheclassicalimplementation.

Tomakedecoratorscooperatewithothersaswe'dexpect,weneedtohandlethingscarefully:

functionprefix(

target:Object,

name:string,

descriptor:PropertyDescriptor

):PropertyDescriptor{

letmethod=descriptor.valueasFunction;

if(typeofmethod!=='function'){

thrownewError('Expectingdecoratingamethod');

}

return{

value:function(){

return'[prefix]'+method.apply(this,arguments);

Page 551: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

},

enumerable:descriptor.enumerable,

configurable:descriptor.configurable,

writable:descriptor.writable

};

}

Note

IncurrentECMAScriptdecoratorproposal,whendecoratingamethodorproperty(usuallywithgetterorsetter),youwillhavethethirdargumentpassedinasthepropertydescriptor.

Note

Checkoutthefollowinglinkformoreinformationaboutpropertydescriptors:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty.

Thesuffixdecoratorwouldbejustliketheprefixdecorator.SoI'llsavethecodelineshere.

Page 552: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesThekeytotheDecoratorPatternisbeingabletoaddfunctionalitiesdynamically,anddecoratorsareusuallyexpectedtoplaynicewitheachother.ThoseexpectationsofDecoratorPatternmakeitreallyflexibletoformacustomizedobject.However,itwouldbehardforcertaintypesofdecoratorstoactuallyworkwelltogether.

Considerdecoratinganobjectwithmultipledecoratorsjustlikethesecondexampleofimplementation,wouldthedecoratingordermatter?Orshouldthedecoratingordermatter?

Aproperlywrittendecoratorshouldalwaysworknomatterwhereitisinthedecoratorslist.Andit'susuallypreferredthatthedecoratedtargetbehavesalmostthesamewithdecoratorsdecoratedindifferentorders.

Page 553: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AdapterPatternAdapterPatternconnectsexistingclassesorobjectswithanotherexistingclient.Itmakesclassesthatarenotdesignedtoworktogetherpossibletocooperatewitheachother.

Anadaptercouldbeeitheraclassadapteroranobjectadapter.AclassadapterextendstheadapteeclassandexposesextraAPIsthatwouldworkwiththeclient.Anobjectadapter,ontheotherhand,doesnotextendtheadapteeclass.Instead,itstorestheadapteeasadependency.

Theclassadapterisusefulwhenyouneedtoaccessprotectedmethodsorpropertiesoftheadapteeclass.However,italsohassomerestrictionswhenitcomestotheJavaScriptworld:

TheadapteeclassneedstobeextendableIftheclienttargetisanabstractclassotherthanpureinterface,youcan'textendtheadapteeclassandtheclienttargetwiththesameadapterclasswithoutamixinAsingleclasswithtwosetsofmethodsandpropertiescouldbeconfusing

Duetothoselimitations,wearegoingtotalkmoreaboutobjectadapters.Takingbrowser-sidestorageforexample,we'llassumewehaveaclientworkingwithstorageobjectsthathavebothmethodsgetandsetwithcorrectsignatures(forexample,astoragethatstoresdataonlinethroughAJAX).NowwewanttheclienttoworkwithIndexedDBforfasterresponseandofflineusage;we'llneedtocreateanadapterforIndexedDBthatgetsandsetsdata:

Page 554: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WearegoingtousePromiseforreceivingresultsorerrorsofasynchronousoperations.SeethefollowinglinkformoreinformationifyouarenotyetfamiliarwithPromise:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise.

Page 555: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofAdapterPatterninclude:

Target:Storage

DefinestheinterfaceofexistingtargetsthatworkswithclientAdaptee:IndexedDB

TheimplementationthatisnotdesignedtoworkwiththeclientAdapter:IndexedDBStorage

ConformstheinterfaceoftargetandinteractswithadapteeClient.

Manipulatesthetarget

Page 556: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeAdapterPatterncanbeappliedwhentheexistingclientclassisnotdesignedtoworkwiththeexistingadaptees.Itfocusesontheuniqueadapterpartwhenapplyingtodifferentcombinationsofclientsandadaptees.

Page 557: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationStartwiththeStorageinterface:

interfaceStorage{

get<T>(key:string):Promise<T>;

set<T>(key:string,value:T):Promise<void>;

}

Note

Wedefinedthegetmethodwithgeneric,sothatifweneitherspecifythegenerictype,norcastthevaluetypeofareturnedPromise,thetypeofthevaluewouldbe{}.Thiswouldprobablyfailfollowingtypechecking.

WiththehelpofexamplesfoundonMDN,wecannowsetuptheIndexedDBadapter.VisitIndexedDBStorage:https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB.

ThecreationofIndexedDBinstancesisasynchronous.Wecouldputtheopeningoperationinsideagetorsetmethodsothedatabasecanbeopenedondemand.Butfornow,let'smakeiteasierbycreatinganinstanceofIndexedDBStoragethathasadatabaseinstancewhichisalreadyopened.

However,constructorsusuallydon'thaveasynchronouscode.Eveniftheydo,itcannotapplychangestotheinstancebeforecompletingtheconstruction.Fortunately,FactoryMethodPatternworkswellwithasynchronousinitiation:

classIndexedDBStorageimplementsStorage{

constructor(

publicdb:IDBDatabase,

publicstoreName='default'

){}

open(name:string):Promise<IndexedDBStorage>{

returnnewPromise<IndexedDBStorage>(

(resolve,reject)=>{

letrequest=indexedDB.open(name);

//...

});

}

}

InsidethePromiseresolverofmethodopen,we'llgettheasynchronousworkdone:

letrequest=indexedDB.open(name);

request.onsuccess=event=>{

letdb=request.resultasIDBDatabase;

letstorage=newIndexedDBStorage(db);

Page 558: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

resolve(storage);

};

request.onerror=event=>{

reject(request.error);

};

NowwhenweareaccessinganinstanceofIndexedDBStorage,wecanassumeithasanopeneddatabaseandisreadytomakequeries.Tomakechangesortogetvaluesfromthedatabase,weneedtocreateatransaction.Here'show:

get<T>(key:string):Promise<T>{

returnnewPromise<T>((resolve,reject)=>{

lettransaction=this.db.transaction(this.storeName);

letstore=transaction.objectStore(this.storeName);

letrequest=store.get(key);

request.onsuccess=event=>{

resolve(request.result);

};

request.onerror=event=>{

reject(request.error);

};

});

}

Methodsetissimilar.Butwhilethetransactionisbydefaultread-only,weneedtoexplicitlyspecify'readwrite'mode.

set<T>(key:string,value:T):Promise<void>{

returnnewPromise<void>((resolve,reject)=>{

lettransaction=

this.db.transaction(this.storeName,'readwrite');

letstore=transaction.objectStore(this.storeName);

letrequest=store.put(value,key);

request.onsuccess=event=>{

resolve();

};

request.onerror=event=>{

reject(request.error);

};

});

}

Andnowwecanhaveadrop-inreplacementforthepreviousstorageusedbytheclient.

Page 559: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesByapplyingAdapterPattern,wecanfillthegapbetweenclassesthatoriginallywouldnotworktogether.Inthissituation,AdapterPatternisquiteastraightforwardsolutionthatmightcometomind.

ButinotherscenarioslikeadebuggeradapterfordebuggingextensionsofanIDE,theimplementationofAdapterPatterncouldbemorechallenging.

Page 560: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BridgePatternBridgePatterndecouplestheabstractionmanipulatedbyclientsfromfunctionalimplementationsandmakesitpossibletoaddorreplacetheseabstractionsandimplementationseasily.

Takeasetofcross-APIUIelementsasanexample:

WehavetheabstractionUIElementthatcanaccessdifferentimplementationsofUIToolkitforcreatingdifferentUIbasedoneitherSVGorcanvas.Intheprecedingstructure,thebridgeistheconnectionbetweenUIElementandUIToolkit.

Page 561: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofBridgePatterninclude:

Abstraction:UIElement

Definestheinterfaceofobjectstobemanipulatedbytheclientandstoresthereferencetoitsimplementer.Refinedabstraction:TextElement,ImageElement

Extendsabstractionwithspecializedbehaviors.Implementer:UIToolkit

Definestheinterfaceofageneralimplementerthatwilleventuallycarryouttheoperationsdefinedinabstractions.Theimplementerusuallycaresonlyaboutbasicoperationswhiletheabstractionwillhandlehigh-leveloperations.Concreteimplementer:SVGToolkit,CanvasToolkit

Implementstheimplementerinterfaceandmanipulateslow-levelAPIs.

Page 562: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeAlthoughhavingabstractionandimplementerdecoupledprovidesBridgePatternwiththeabilitytoworkwithseveralabstractionsandimplementers,mostofthetime,bridgepatternsworkonlywithasingleimplementer.

Ifyoutakeacloserlook,youwillfindBridgePatternisextremelysimilartoAdapterPattern.However,whileAdapterPatterntriestomakeexistingclassescooperateandfocusesontheadapterspart,BridgePatternforeseesthedivergencesandprovidesawell-thought-outanduniversalinterfaceforitsabstractionsthatplaythepartofadapters.

Page 563: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationAworkingimplementationcouldbenon-trivialintheexamplewearetalkingabout.Butwecanstillsketchouttheskeletoneasily.

StartwithimplementerUIToolkitandabstractionUIElementthataredirectlyrelatedtothebridgeconcept:

interfaceUIToolkit{

drawBorder():void;

drawImage(src:string):void;

drawText(text:string):void;

}

abstractclassUIElement{

constructor(

publictoolkit:UIToolkit

){}

abstractrender():void;

}

AndnowwecanextendUIElementforrefinedabstractionswithdifferentbehaviors.FirsttheTextElementclass:

classTextElementextendsUIElement{

constructor(

publictext:string,

toolkit:UIToolkit

){

super(toolkit);

}

render():void{

this.toolkit.drawText(this.text);

}

}

AndtheImageElementclasswithsimilarcode:

classImageElementextendsUIElement{

constructor(

publicsrc:string,

toolkit:UIToolkit

){

super(toolkit);

}

render():void{

this.toolkit.drawImage(this.src);

}

}

Page 564: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BycreatingconcreteUIToolkitsubclasses,wecanmanagetomakeeverythingtogetherwiththeclient.Butasitcouldleadtohardworkwewouldnotwanttotouchnow,we'llskipitbyusingavariablepointingtoundefinedinthisexample:

lettoolkit:UIToolkit;

letimageElement=newImageElement('foo.jpg',toolkit);

lettextElement=newTextElement('bar',toolkit);

imageElement.render();

textElement.render();

Intherealworld,therenderpartcouldalsobeaheavylift.Butasit'scodedatarelativelyhigher-level,ittorturesyouinadifferentway.

Page 565: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesDespitehavingcompletelydifferentnamesfortheabstraction(UIElement)intheexampleaboveandtheadapterinterface(Storage),theyplaysimilarrolesinastaticcombination.

However,aswementionedinthepatternscopesection,theintentionsofBridgePatternandAdapterPatterndiffer.

Bydecouplingtheabstractionandimplementer,BridgePatternbringsgreatextensibilitytothesystem.Theclientdoesnotneedtoknowabouttheimplementationdetails,andthishelpstobuildmorestablesystemsasitformsahealthierdependencystructure.

AnotherbonusthatmightbebroughtbyBridgePatternisthat,withaproperlyconfiguredbuildprocess,itcanreducecompilationtimeasthecompilerdoesnotneedtoknowinformationontheotherendofthebridgewhenchangesaremadetoarefinedabstractionorconcreteimplementer.

Page 566: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FaçadePatternTheFaçadePatternorganizessubsystemsandprovidesaunifiedhigher-levelinterface.Anexamplethatmightbefamiliartoyouisamodularsystem.InJavaScript(andofcourseTypeScript),peopleusemodulestoorganizecode.Amodularsystemmakesprojectseasiertomaintain,asacleanprojectstructurecanhelprevealtheinterconnectionsamongdifferentpartsoftheproject.

Itiscommonthatoneprojectgetsreferencedbyothers,butobviouslytheprojectthatreferencesotherprojectsdoesn'tandshouldn'tcaremuchabouttheinnerstructuresofitsdependencies.Thusafaçadecanbeintroducedforadependencyprojecttoprovideahigher-levelAPIandexposewhatreallymatterstoitsdependents.

Takearobotasanexample.Peoplewhobuildarobotanditscomponentswillneedtocontroleverypartseparatelyandletthemcooperateatthesametime.However,peoplewhowanttousethisrobotwouldonlyneedtosendsimplecommandslike"walk"and"jump".

Forthemostflexibleusage,therobot"SDK"canprovideclasseslikeMotionController,FeedbackController,Thigh,Shank,Footandsoon.Possiblylikethefollowingimageshows:

Butcertainly,mostofthepeoplewhowanttocontrolorprogramthisrobotdonotwanttoknowasmanydetailsasthis.Whattheyreallywantisnotafancytoolboxwitheverything

Page 567: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

inbox,butjustanintegralrobotthatfollowstheircommands.Thustherobot"SDK"canactuallyprovideafaçadethatcontrolstheinnerpiecesandexposesmuchsimplerAPIs:

Unfortunately,FaçadePatternleavesusanopenquestionofhowtodesignthefaçadeAPIandsubsystems.Answeringthisquestionproperlyisnoteasywork.

Page 568: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofaFaçadePatternarerelativelysimplewhenitcomestotheircategories:

Façade:Robot

Definesasetofhigher-levelinterfaces,andmakessubsystemscooperate.Subsystems:MotionController,FeedbackController,Thigh,ShankandFoot

Implementstheirownfunctionalitiesandcommunicatesinternallywithothersubsystemsifnecessary.Subsystemsaredependenciesofafaçade,andtheydonotdependonthefaçade.

Page 569: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeFaçadesusuallyactasjunctionsthatconnectahigher-levelsystemanditssubsystems.ThekeytotheFaçadePatternistodrawalinebetweenwhatadependentshouldorshouldn'tcareaboutofitsdependencies.

Page 570: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationConsiderputtinguparobotwithitsleftandrightlegs,wecanactuallyaddanotherabstractionlayercalledLegthatmanagesThigh,Shank,andFoot.Ifwearegoingtoseparatemotionandfeedbackcontrollerstodifferentlegsrespectively,wemayalsoaddthosetwoaspartoftheLeg:

classLeg{

thigh:Thigh;

shank:Shank;

foot:Foot;

motionController:MotionController;

feedbackController:FeedbackController;

}

BeforeweaddmoredetailstoLeg,let'sfirstdefineMotionControllerandFeedbackController.

TheMotionControllerissupposedtocontrolawholelegbasedonavalueorasetofvalues.Herewearesimplifyingthatasasingleanglefornotbeingdistractedbythisimpossiblerobot:

classMotionController{

constructor(

publicleg:Leg

){}

setAngle(angle:number):void{

let{

thigh,

shank,

foot

}=this.leg;

//...

}

}

AndtheFeedbackControllerissupposedtobeaninstanceofEventEmitterthatreportsthestatechangesorusefulevents:

import{EventEmitter}from'events';

classFeedbackControllerextendsEventEmitter{

constructor(

publicfoot:Foot

){

super();

}

}

Page 571: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NowwecanmakeclassLegrelativelycomplete:

classLeg{

thigh=newThigh();

shank=newShank();

foot=newFoot();

motionController:MotionController;

feedbackController:FeedbackController;

constructor(){

this.motionController=

newMotionController(this);

this.feedbackController=

newFeedbackController(this.foot);

this.feedbackController.on('touch',()=>{

//...

});

}

}

Let'sputtwolegstogethertosketchtheskeletonofarobot:

classRobot{

leftLegMotion:MotionController;

rightLegMotion:MotionController;

leftFootFeedback:FeedbackController;

rightFootFeedback:FeedbackController;

walk(steps:number):void{}

jump(strength:number):void{}

}

I'momittingthedefinitionofclassesThigh,Shank,andFootaswearenotactuallygoingtowalktherobot.NowforauserthatonlywantstowalkorjumparobotviasimpleAPI,theycanmakeitviatheRobotobjectthathaseverythingconnected.

Page 572: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesFaçadePatternloosensthecouplingbetweenclientandsubsystems.Thoughitdoesnotdecouplethemcompletelyasyouwillprobablystillneedtoworkwithobjectsdefinedinsubsystems.

Façadesusuallyforwardoperationsfromclienttopropersubsystemsorevendoheavyworktomakethemworktogether.

WiththehelpofFaçadePattern,thesystemandtherelationshipandstructurewithinthesystemcanstaycleanandintuitive.

Page 573: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FlyweightPatternAflyweightinFlyweightPatternisastatelessobjectthatcanbesharedacrossobjectsormaybeclassesmanytimes.Obviously,thatsuggestsFlyweightPatternisapatternaboutmemoryefficiencyandmaybeperformanceiftheconstructionofobjectsisexpensive.

Takingdrawingsnowflakesasanexample.Despiterealsnowflakesbeingdifferenttoeachother,whenwearetryingtodrawthemontocanvas,weusuallyhavealimitednumberofstyles.However,byaddingpropertieslikesizesandtransformations,wecancreateabeautifulsnowscenewithlimitedsnowflakestyles.

Asaflyweightisstateless,ideallyitallowsmultipleoperationssimultaneously.Youmightneedtobecautiouswhenworkingwithmulti-threadstuff.Fortunately,JavaScriptisusuallysingle-threadedandavoidsthisissueifallrelatedcodeissynchronous.Youwillstillneedtotakecareindetailedscenariosifyourcodeisworkingasynchronously.

AssumewehavesomeflyweightsofclassSnowflake:

Whenitsnows,itwouldlooklikethis:

Page 574: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Intheimageabove,snowflakesindifferentstylesaretheresultofrenderingwithdifferentproperties.

It'scommonthatwewouldhavestylesandimageresourcesbeingloadeddynamically,thuswecoulduseaFlyweightFactoryforcreatingandmanagingflyweightobjects.

Page 575: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsThesimplestimplementationofFlyweightPatternhasthefollowingparticipants:

Flyweight:Snowflake

Definestheclassofflyweightobjects.Flyweightfactory:FlyweightFactory

Createsandmanagesflyweightobjects.Client.

Storesstatesoftargetsandusesflyweightobjectstomanipulatethesetargets.

Withtheseparticipants,weassumethatthemanipulationcouldbeaccomplishedthroughflyweightswithdifferentstates.Itwouldalsobehelpfulsometimestohaveconcreteflyweightclassallowingcustomizedbehaviors.

Page 576: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeFlyweightPatternisaresultofeffortstoimprovingmemoryefficiencyandperformance.Theimplementationcaresabouthavingtheinstancesbeingstateless,anditisusuallytheclientthatmanagesdetailedstatesfordifferenttargets.

Page 577: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationWhatmakesFlyweightPatternusefulinthesnowflakeexampleisthatasnowflakewiththesamestyleusuallysharesthesameimage.Theimageiswhatconsumestimetoloadandoccupiesnotablememory.

WearestartingwithafakeImageclassthatpretendstoloadimages:

classImage{

constructor(url:string){}

}

TheSnowflakeclassinourexamplehasonlyasingleimageproperty,andthatisapropertythatwillbesharedbymanysnowflakestobedrawn.Astheinstanceisnowstateless,parametersfromcontextarerequiredforrendering:

classSnowflake{

image:Image;

constructor(

publicstyle:string

){

leturl=style+'.png';

this.image=newImage(url);

}

render(x:number,y:number,angle:number):void{

//...

}

}

Theflyweightsaremanagedbyafactoryforeasieraccessing.We'llhaveaSnowflakeFactorythatcachescreatedsnowflakeobjectswithcertainstyles:

consthasOwnProperty=Object.prototype.hasOwnProperty;

classSnowflakeFactory{

cache:{

[style:string]:Snowflake;

}={};

get(style:string):Snowflake{

letcache=this.cache;

letsnowflake:Snowflake;

if(hasOwnProperty.call(cache,style)){

snowflake=cache[style];

}else{

snowflake=newSnowflake(style);

cache[style]=snowflake;

}

returnsnowflake;

Page 578: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

}

Withbuildingblocksready,we'llimplementtheclient(Sky)thatsnows:

constSNOW_STYLES=['A','B','C'];

classSky{

constructor(

publicwidth:number,

publicheight:number

){}

snow(factory:SnowflakeFactory,count:number){}

}

Wearegoingtofilltheskywithrandomsnowflakesatrandompositions.Beforethatlet'screateahelperfunctionthatgeneratesanumberbetween0andamaxvaluegiven:

functiongetRandomInteger(max:number):number{

returnMath.floor(Math.random()*max);

}

AndthencompletemethodsnowofSky:

snow(factory:SnowflakeFactory,count:number){

letstylesCount=SNOW_STYLES.length;

for(leti=0;i<count;i++){

letstyle=SNOW_STYLES[getRandomInteger(stylesCount)];

letsnowflake=factory.get(style);

letx=getRandomInteger(this.width);

lety=getRandomInteger(this.height);

letangle=getRandomInteger(60);

snowflake.render(x,y,angle);

}

}

NowwemayhavethousandsofsnowflakesintheskybutwithonlythreeinstancesofSnowflakecreated.Youcancontinuethisexamplebystoringthestateofsnowflakesandanimatingthesnowing.

Page 579: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesFlyweightPatternreducesthetotalnumberofobjectsinvolvedinasystem.Asadirectresult,itmaysavequitealotmemory.Thissavingbecomesmoresignificantwhentheflyweightsgetusedbytheclientthatprocessesalargenumberoftargets.

FlyweightPatternalsobringsextralogicintothesystem.Whentouseornottousethispatternisagainabalancinggamebetweendevelopmentefficiencyandruntimeefficiencyfromthispointofview.Thoughmostofthetime,ifthere'snotagoodreason,wegowithdevelopmentefficiency.

Page 580: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ProxyPatternProxyPatternapplieswhentheprogramneedstoknowaboutortointervenethebehaviorofaccessingobjects.ThereareseveraldetailedscenariosinProxyPattern,andwecandistinguishthosescenariosbytheirdifferentpurposes:

Remoteproxy:Aproxywithinterfacetomanipulateremoteobjects,suchasdataitemsonaremoteserverVirtualproxy:AproxythatmanagesexpensiveobjectswhichneedtobeloadedondemandProtectionproxy:Aproxythatcontrolsaccesstotargetobjects,typicallyitverifiespermissionsandvalidatesvaluesSmartproxy:Aproxythatdoesadditionaloperationswhenaccessingtargetobjects

InthesectionofAdapterPattern,weusedfactorymethodopenthatcreatesanobjectasynchronously.Asatrade-off,wehadtolettheclientwaitbeforetheobjectgetscreated.

WithProxyPattern,wecouldnowopendatabaseondemandandcreatestorageinstancessynchronously.

Note

Aproxyisusuallydedicatedtoobjectorobjectswithknownmethodsandproperties.Butwith

Page 581: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thenewProxyAPIprovidedinES6,wecangetmoreinterestingthingsdonebygettingtoknowwhatmethodsorpropertiesarebeingaccessed.Pleaserefertothefollowinglinkformoreinformation:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy.

Page 582: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofProxyPatterninclude:

Proxy:IndexedDBStorage

Definesinterfaceandimplementsoperationstomanageaccesstothesubject.Subject:IndexedDB

Thesubjecttobeaccessedbyproxy.Client:Accessessubjectviaproxy.

Page 583: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeDespitehavingasimilarstructuretoAdapterPattern,thekeyofProxyPatternistointervenetheaccesstotargetobjectsratherthantoadaptanincompatibleinterface.Sometimesitmightchangetheresultofaspecificmethodorthevalueofacertainproperty,butthatisprobablyforfallingbackorexceptionhandlingpurposes.

Page 584: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationTherearetwodifferenceswe'llhaveinthisimplementationcomparedtotheexampleforpureAdapterPattern.First,we'llcreatetheIndexedDBStorageinstancewithaconstructor,andhavethedatabaseopenedondemand.Second,wearegoingtoaddauselesspermissioncheckingformethodsgetandset.

Nowwhenwecallthemethodgetorset,thedatabasecouldeitherhavebeenopenedornot.Promiseisagreatchoiceforrepresentingavaluethatmighteitherbependingorsettled.Considerthisexample:

letready=newPromise<string>(resolve=>{

setTimeout(()=>{

resolve('biu~');

},Math.random()*1000);

});

setTimeout(()=>{

ready.then(text=>{

console.log(text);

});

},999);

It'shardtotellwhetherPromisereadyisfulfilledwhenthesecondtimeoutfires.Buttheoverallbehavioriseasytopredict:itwilllogthe'biu~'textinaround1second.ByreplacingthePromisevariablereadywithamethodorgetter,itwouldbeabletostarttheasynchronousoperationonlywhenneeded.

Solet'sstarttherefactoringofclassIndexedDBStoragewiththegetterthatcreatesthePromiseofthedatabasetobeopened:

privatedbPromise:Promise<IDBDatabase>;

constructor(

publicname:string,

publicstoreName='default'

){}

privategetdbReady():Promise<IDBDatabase>{

if(!this.dbPromise){

this.dbPromise=

newPromise<IDBDatabase>((resolve,reject)=>{

letrequest=indexedDB.open(name);

request.onsuccess=event=>{

resolve(request.result);

};

request.onerror=event=>{

reject(request.error);

};

});

Page 585: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

returnthis.dbPromise;

}

NowthefirsttimeweaccesspropertydbReady,itwillopenthedatabaseandcreateaPromisethatwillbefulfilledwiththedatabasebeingopened.Tomakethisworkwithmethodsgetandset,wejustneedtowrapwhatwe'veimplementedintoathenmethodfollowingthedbReadyPromise.

Firstformethodget:

get<T>(key:string):Promise<T>{

returnthis

.dbReady

.then(db=>newPromise<T>((resolve,reject)=>{

lettransaction=db.transaction(this.storeName);

letstore=transaction.objectStore(this.storeName);

letrequest=store.get(key);

request.onsuccess=event=>{

resolve(request.result);

};

request.onerror=event=>{

reject(request.error);

};

}));

}

Andfollowedbyupdatedmethodset:

set<T>(key:string,value:T):Promise<void>{

returnthis

.dbReady

.then(db=>newPromise<void>((resolve,reject)=>{

lettransaction=db

.transaction(this.storeName,'readwrite');

letstore=transaction.objectStore(this.storeName);

letrequest=store.put(value,key);

request.onsuccess=event=>{

resolve();

};

request.onerror=event=>{

reject(request.error);

};

}));

}

NowwefinallyhavetheIndexedDBStoragepropertythatcandoarealdrop-inreplacement

Page 586: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

fortheclientthatsupportstheinterface.Wearealsogoingtoaddsimplepermissioncheckingwithaplainobjectthatdescribesthepermissionofreadandwrite:

interfacePermission{

write:boolean;

read:boolean;

}

Thenwewilladdpermissioncheckingformethodgetandsetseparately:

get<T>(key:string):Promise<T>{

if(!this.permission.read){

returnPromise.reject<T>(newError('Permissiondenied'));

}

//...

}

set<T>(key:string,value:T):Promise<void>{

if(!this.permission.write){

returnPromise.reject(newError('Permissiondenied'));

}

//...

}

YoumayrecallDecoratorPatternwhenyouarethinkingaboutthepermissioncheckingpart,anddecoratorscouldbeusedtosimplifythelineswritten.Trytousedecoratorsyntaxtoimplementthispermissioncheckingyourself.

Page 587: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesTheimplementationofProxyPatterncanusuallybetreatedastheencapsulationoftheoperationstospecificobjectsortargets.Itiseasytohavetheencapsulationaugmentedwithoutextraburdenontheclient.

Forexample,aworkingonlinedatabaseproxycoulddomuchmorethanjustactinglikeaplainsurrogate.Itmaycachedataandchangeslocally,orsynchronizeonschedulewithouttheclientbeingaware.

Page 588: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,welearnedaboutstructuraldesignpatternsincludingComposite,Decorator,Adapter,Bridge,Façade,Flyweight,andProxy.Againwefoundsomeofthesepatternsarehighlyinterrelatedandevensimilartoeachothertosomedegree.

Forexample,wemixedCompositePatternwithDecoratorPattern,AdapterPatternwithProxyPattern,comparedAdapterPatternandBridgePattern.Duringthejourneyofexploring,wesometimesfounditwasjustanaturalresulttohaveourcodeendinapatternthat'ssimilartowhatwe'velistedifwetookwritingbettercodeintoconsideration.

TakingAdapterPatternandBridgePatternasanexample,whenwearetryingtomaketwoclassescooperate,itcomesoutwithAdapterPatternandwhenweareplanningonconnectingwithdifferentclassesinadvance,itgoeswithBridgePattern.Therearenoactuallinesbetweeneachpatternandtheapplicationsofthosepatterns,thoughthetechniquesbehindpatternscouldusuallybeuseful.

Inthenextchapter,wearegoingtotalkaboutbehavioralpatternsthathelptoformalgorithmsandassigntheresponsibilities.

Page 589: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter5.BehavioralDesignPatternsAsthenamesuggests,behavioraldesignpatternsarepatternsabouthowobjectsorclassesinteractwitheachother.Theimplementationofbehavioraldesignpatternsusuallyrequirescertaindatastructurestosupporttheinteractioninasystem.However,behavioralpatternsandstructuralpatternsfocusondifferentaspectswhenapplied.Asaresult,youmightfindpatternsinthecategoryofbehavioraldesignpatternsusuallyhavesimplerormorestraightforwardstructurescomparedtostructuraldesignpatterns.

Inthischapter,wearegoingtotalkaboutsomeofthefollowingcommonbehavioralpatterns:

ChainofResponsibility:OrganizesbehaviorswithdifferentscopesCommand:ExposescommandsfromtheinternalwithencapsulatedcontextMemento:ProvidesanapproachformanagingstatesoutsideoftheirownerswithoutexposingdetailedimplementationsIterator:ProvidesauniversalinterfacefortraversingMediator:Itgroupscouplingandlogicallyrelatedobjectsandmakesinterconnectionscleanerinasystemthatmanagesmanyobjects

Page 590: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChainofResponsibilityPatternTherearemanyscenariosunderwhichwemightwanttoapplycertainactionsthatcanfallbackfromadetailedscopetoamoregeneralone.

AniceexamplewouldbethehelpinformationofaGUIapplication:whenauserrequestshelpinformationforacertainpartoftheuserinterface,itisexpectedtoshowinformationasspecificaspossible.Thiscanbedonewithdifferentimplementations,andthemostintuitiveoneforawebdevelopercouldbeeventsbubbling.

ConsideraDOMstructurelikethis:

<divclass="outer">

<divclass="inner">

<spanclass="origin"></span>

</div>

</div>

Ifauserclicksonthespan.originelement,aclickeventwouldstartbubblingfromthespanelementtothedocumentroot(ifuseCaptureisfalse):

$('.origin').click(event=>{

console.log('Clickon`span.origin`.');

});

$('.outer').click(event=>{

console.log('Clickon`div.outer`.');

});

Bydefault,itwilltriggerbotheventlistenersaddedintheprecedingcode.Tostopthepropagationassoonasaneventgetshandled,wecancallitsstopPropagationmethod:

$('.origin').click(event=>{

console.log('Clickon`span.origin`.');

event.stopPropagation();

});

$('.outer').click(event=>{

Console.log('Clickon`div.outer`.');

});

Thoughaclickeventisnotexactlythesameasthehelpinformationrequest,withthesupportofcustomevents,it'squiteeasytohandlehelpinformationwithnecessarydetailedorgeneralinformationinthesamechain.

AnotherimportantimplementationoftheChainofResponsibilityPatternisrelatedtoerrorhandling.Aprimitiveexampleforthiscouldbeusingtry...catch.Considercodelikethis:wehavethreefunctions:foo,bar,andbiu,fooiscalledbybarwhilebariscalledbybiu:

functionfoo(){

Page 591: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

//throwsomeerrors.

}

functionbar(){

foo();

}

functionbiu(){

bar();

}

biu();

Insidebothfunctionsbarandbiu,wecandosomeerrorcatching.Assumingfunctionfoothrowstwokindsoferrors:

functionfoo(){

letvalue=Math.random();

if(value<0.5){

thrownewError('Awesomeerror');

}elseif(value<0.8){

thrownewTypeError('Awesometypeerror');

}

}

InfunctionbarwewouldliketohandletheTypeErrorandleaveothererrorsthrowingon:

functionbar(){

try{

foo();

}catch(error){

if(errorinstanceofTypeError){

console.log('Sometypeerroroccurs',error);

}else{

throwerror;

}

}

}

Andinfunctionbiu,wewouldliketoaddmoregeneralhandlingthatcatchesalltheerrorssothattheprogramwillnotcrash:

functionbiu(){

try{

bar();

}catch(error){

console.log('Someerroroccurs',error);

}

}

Sobyusingtry...catchstatements,youmayhavebeenusingtheChainofResponsibilityPatternconstantlywithoutpayinganyattentiontoit.Justlikeyoumayhavebeenusingotherwell-knowndesignpatternsallthetime.

Page 592: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IfweabstractthestructureofChainofResponsibilityPatternintoobjects,wecouldhavesomethingasillustratedinthefigure:

Page 593: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsoftheChainofResponsibilityPatterninclude:

Handler:Definestheinterfaceofthehandlerwithsuccessorandmethodtohandlerequests.ThisisdoneimplicitlywithclasseslikeEventEmitterandtry...catchsyntax.Concretehandler:EventListener,catchblockandHandlerA/HandlerBintheclassversion.Defineshandlersintheformofcallbacks,codeblocksandclassesthathandlerequests.Client:Initiatestherequeststhatgothroughthechain.

Page 594: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeTheChainofResponsibilityPatternitselfcouldbeappliedtomanydifferentscopesinaprogram.Itrequiresamulti-levelchaintowork,butthischaincouldbeindifferentforms.We'vebeenplayingwitheventsaswellastry...catchstatementsthathavestructurallevels,thispatterncouldalsobeappliedtoscenariosthathavelogicallevels.

Considerobjectsmarkedwithdifferentscopesusingstring:

letobjectA={

scope:'user.installation.package'

};

letobjectB={

scope:'user.installation'

};

Nowwehavetwoobjectswithrelatedscopesspecifiedbystring,andbyaddingfilterstothesescopestrings,wecanapplyoperationsfromspecificonestogeneralones.

Page 595: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationInthispart,wearegoingtoimplementtheclassversionwe'vementionedattheendoftheintroductiontotheChainofResponsibilityPattern.Considerrequeststhatcouldeitheraskforhelpinformationorfeedbackprompts:

typeRequestType='help'|'feedback';

interfaceRequest{

type:RequestType;

}

Note

Weareusingstringliteraltypeherewithuniontype.ItisaprettyusefulfeatureprovidedinTypeScriptthatplayswellwithexistingJavaScriptcodingstyles.Seethefollowinglinkformoreinformation:http://www.typescriptlang.org/docs/handbook/advanced-types.html.

Oneofthekeyprocessesforthispatternisgoingthroughthehandlers'chainandfindingoutthemostspecifichandlerthat'savailablefortherequest.Thereareseveralwaystoachievethis:byrecursivelyinvokingthehandlemethodofasuccessor,orhavingaseparatelogicwalkingthroughthehandlersuccessorchainuntiltherequestisconfirmedashandled.

Thelogicwalkingthroughthechaininthesecondwayrequirestheacknowledgmentofwhetherarequesthasbeenproperlyhandled.Thiscanbedoneeitherbyastateindicatorontherequestobjectorbythereturnvalueofthehandlemethod.

We'llgowiththerecursiveimplementationinthispart.Firstly,wewantthedefaulthandlingbehaviorofahandlertobeforwardingrequeststoitssuccessorifany:

classHandler{

privatesuccessor:Handler;

handle(request:Request):void{

if(this.successor){

this.successor.handle(request);

}

}

}

AndnowforHelpHandler,ithandleshelprequestsbutforwardsothers:

classHelpHandlerextendsHandler{

handle(request:Request):void{

if(request.type==='help'){

//Showhelpinformation.

}else{

super.handle(request);

}

}

}

Page 596: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThecodeforFeedbackHandlerissimilar:

classFeedbackHandlerextendsHandler{

handle(request:Request):void{

if(request.type==='feedback'){

//Promptforfeedback.

}else{

super.handle(request);

}

}

}

Thus,achainofhandlerscouldbemadeupinsomeway.Andifarequestgotinthischain,itwouldbepassedonuntilahandlerrecognizesandhandlesit.However,itisnotnecessarytohaveallrequestshandledafterprocessingthem.Thehandlerscanalwayspassarequestonwhetherthisrequestgetsprocessedbythishandlerornot.

Page 597: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesTheChainofResponsibilityPatterndecouplestheconnectionbetweenobjectsthatissuetherequestsandlogicthathandlesthoserequests.Thesenderassumesthatitsrequestscould,butnotnecessarily,beproperlyhandledwithoutknowingthedetails.Forsomeimplementations,itisalsoveryeasytoaddnewresponsibilitiestoaspecifichandleronthechain.Thisprovidesnotableflexibilityforhandlingrequests.

Besidestheexampleswe'vebeentalkingabout,thereisanotherimportantmutationoftry...catchthatcanbetreatedintheChainofResponsibilityPattern-Promise.Withinasmallerscope,thechaincouldberepresentedas:

promise

.catch(TypeError,reason=>{

//handlesTypeError.

})

.catch(ReferenceError,reason=>{

//handlesReferenceError.

})

.catch(reason=>{

//handlesothererrors.

});

Note

ThestandardcatchmethodonanESPromiseobjectdoesnotprovidetheoverloadthatacceptsanerrortypeasaparameter,butmanyimplementationsdo.

Inalargerscope,thischainwouldusuallyappearwhenthecodeisplayingwiththird-partylibraries.Acommonusagewouldbeconvertingerrorsproducedbyotherlibrariestoerrorsknowntothecurrentproject.We'lltalkmoreabouterrorhandlingofasynchronouscodelaterinthisbook.

Page 598: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CommandPatternCommandPatterninvolvesencapsulatingoperationsasexecutablecommandsandcouldeitherbeintheformofobjectsorfunctionsinJavaScript.Itiscommonthatwemaywanttomakeoperationsrelyoncertaincontextandstatesthatarenotaccessiblefortheinvokers.Bystoringthosepiecesofinformationwithacommandandpassingitout,thissituationcouldbeproperlyhandled.

Consideranextremelysimpleexample:wewanttoprovideafunctioncalledwait,whichreturnsacancelhandler:

functionwait(){

let$layer=$('.wait-layer');

$layer.show();

return()=>{

$layer.hide();

};

}

letcancel=wait();

setTimeout(()=>cancel(),1000);

Thecancelhandlerintheprecedingcodeisjustacommandweweretalkingabout.Itstoresthecontext($layer)usingclosureandispassedoutasthereturnvalueoffunctionwait.

ClosureinJavaScriptprovidesareallysimplewaytostorecommandcontextandstates,however,thedirectdisadvantagewouldbecompromisedflexibilitybetweencontext/statesandcommandfunctionsbecauseclosureislexicallydeterminedandcannotbechangedatruntime.Thiswouldbeokayifthecommandisonlyexpectedtobeinvokedwithfixedcontextandstates,butformorecomplexsituations,wemightneedtoconstructthemasobjectswithaproperdatastructure.

ThefollowingdiagramshowstheoverallrelationsbetweenparticipantsofCommandPattern:

Page 599: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Byproperlysplittingapartcontextandstateswiththecommandobject,CommandPatterncouldalsoplaywellwithFlyweightPatternifyouwantedtoreusecommandobjectsmultipletimes.

OthercommonextensionsbasedonCommandPatternincludeundosupportandmacroswithmultiplecommands.Wearegoingtoplaywiththoselaterintheimplementationpart.

Page 600: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofCommandPatterninclude:

Command:Definesthegeneralinterfaceofcommandspassingaround,itcouldbeafunctionsignatureifthecommandsareintheformoffunctions.Concretecommand:Definesthespecificbehaviorsandrelateddatastructure.ItcouldalsobeafunctionthatmatchesthesignaturedeclaredasCommand.Thecancelhandlerintheveryfirstexampleisaconcretecommand.Context:Thecontextorreceiverthatthecommandisassociatedwith.Inthefirstexample,itisthe$layer.Client:Createsconcretecommandsandtheircontexts.Invoker:Executesconcretecommands.

Page 601: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeCommandPatternsuggeststwoseparatepartsinasingleapplicationoralargersystem:clientandinvoker.Inthesimplifiedexamplewaitandcancel,itcouldbehardtodistinguishthedifferencebetweenthoseparts.Butthelineisclear:clientknowsorcontrolsthecontextofcommandstobeexecutedwith,whileinvokerdoesnothaveaccessordoesnotneedtocareaboutthatinformation.

ThekeytotheCommandPatternistheseparationandbridgingbetweenthosetwopartsthroughcommandsthatstorecontextandstates.

Page 602: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationIt'scommonforaneditortoexposecommandsforthird-partyextensionstomodifythetextcontent.ConsideraTextContextthatcontainsinformationaboutthetextfilebeingeditedandanabstractTextCommandclassassociatedwiththatcontext:

classTextContext{

content='textcontent';

}

abstractclassTextCommand{

constructor(

publiccontext:TextContext

){}

abstractexecute(...args:any[]):void;

}

Certainly,TextContextcouldcontainmuchmoreinformationlikelanguage,encoding,andsoon.Youcanaddtheminyourownimplementationformorefunctionality.Nowwearegoingtocreatetwocommands:ReplaceCommandandInsertCommand.

classReplaceCommandextendsTextCommand{

execute(index:number,length:number,text:string):void{

letcontent=this.context.content;

this.context.content=

content.substr(0,index)+

text+

content.substr(index+length);

}

}

classInsertCommandextendsTextCommand{

execute(index:number,text:string):void{

letcontent=this.context.content;

this.context.content=

content.substr(0,index)+

text+

content.substr(index);

}

}

ThosetwocommandssharesimilarlogicandactuallyInsertCommandcanbetreatedasasubsetofReplaceCommand.Orifwehaveanewdeletecommand,thenreplacecommandcanbetreatedasthecombinationofdeleteandinsertcommands.

Nowlet'sassemblethosecommandswiththeclientandinvoker:

classClient{

privatecontext=newTextContext();

Page 603: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

replaceCommand=newReplaceCommand(this.context);

insertCommand=newInsertCommand(this.context);

}

letclient=newClient();

$('.replace-button').click(()=>{

client.replaceCommand.execute(0,4,'the');

});

$('.insert-button').click(()=>{

client.insertCommand.execute(0,'awesome');

});

Ifwegofurther,wecanactuallyhaveacommandthatexecutesothercommands.Namely,wecanhavemacrocommands.Thoughtheprecedingexamplealonedoesnotmakeitnecessarytocreateamacrocommand,therewouldbescenarioswheremacrocommandshelp.Asthosecommandsarealreadyassociatedwiththeircontexts,amacrocommandusuallydoesnotneedtohaveanexplicitcontext:

interfaceTextCommandInfo{

command:TextCommand,

args:any[];

}

classMacroTextCommand{

constructor(

publicinfos:TextCommandInfo[]

){}

execute():void{

for(letinfoofthis.infos){

info.command.execute(...info.args);

}

}

}

Page 604: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesCommandPatterndecouplestheclient(whoknowsorcontrolscontext)andtheinvoker(whohasnoaccesstoordoesnotcareaboutdetailedcontext).

ItplayswellwithCompositePattern.Considertheexampleofmacrocommandswementionedabove:amacrocommandcanhaveothermacrocommandsasitscomponents,thuswemakeitacompositecommand.

AnotherimportantcaseofCommandPatternisaddingsupportforundooperations.Adirectapproachistoaddtheundomethodtoeverycommand.Whenanundooperationisrequested,invoketheundomethodofcommandsinreverseorder,andwecanpraythateverycommandwouldbeundonecorrectly.However,thisapproachreliesheavilyonaflawlessimplementationoftheundomethodaseverymistakewillaccumulate.Toimplementmorestableundosupport,redundantinformationorsnapshotscouldbestored.

Page 605: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MementoPatternWe'vetalkedaboutanundosupportimplementationintheprevioussectionontheCommandPattern,andfounditwasnoteasytoimplementthemechanismpurelybasedonreversingalltheoperations.However,ifwetakesnapshotsofobjectsastheirhistory,wemaymanagetoavoidaccumulatingmistakesandmakethesystemmorestable.Butthenwehaveaproblem:weneedtostorethestatesofobjectswhilethestatesareencapsulatedwithobjectsthemselves.

MementoPatternhelpsinthissituation.Whileamementocarriesthestateofanobjectatacertaintimepoint,italsocontrolstheprocessofsettingthestatebacktoanobject.Thismakestheinternalstateimplementationhiddenfromtheundomechanisminthefollowingexample:

Wehavetheinstancesofthemementocontrollingthestaterestorationintheprecedingstructure.Itcanalsobecontrolledbythecaretaker,namelytheundomechanism,forsimplestaterestoringcases.

Page 606: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofMementoPatterninclude:

Memento:StoresthestateofanobjectanddefinesmethodrestoreorotherAPIsforrestoringthestatestospecificobjectsOriginator:DealswithobjectsthatneedtohavetheirinternalstatesstoredCaretaker:Managesmementoswithoutinterveningwithwhat'sinside

Page 607: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeMementoPatternmainlydoestwothings:itpreventsthecaretakerfromknowingtheinternalstateimplementationanddecouplesthestateretrievingandrestoringprocessfromstatesmanagedbytheCaretakerorOriginator.

Whenthestateretrievingandrestoringprocessesaresimple,havingseparatedmementosdoesnothelpmuchifyouarealreadykeepingthedecouplingideainmind.

Page 608: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationStartwithanemptyStateinterfaceandMementoclass.AswedonotwantCaretakertoknowthedetailsaboutstateinsideanOriginatororMemento,wewouldliketomakestatepropertyofMementoprivate.HavingrestorationlogicinsideMementodoesalsohelpwiththis,andthusweneedmethodrestore.Sothatwedon'tneedtoexposeapublicinterfaceforreadingstateinsideamemento.

AndasanobjectassignmentinJavaScriptassignsonlyitsreference,wewouldliketodoaquickcopyforthestates(assumingstateobjectsaresingle-level):

interfaceState{}

classMemento{

privatestate:State;

constructor(state:State){

this.state=Object.assign({}asState,state);

}

restore(state:State):void{

Object.assign(state,this.state);

}

}

ForOriginatorweuseagetterandasetterforcreatingandrestoringspecificmementos:

classOriginator{

state:State;

getmemento():Memento{

returnnewMemento(this.state);

}

setmemento(memento:Memento){

memento.restore(this.state);

}

}

NowtheCaretakerwouldmanagethehistoryaccumulatedwithmementos:

classCaretaker{

originator:Originator;

history:Memento[]=[];

save():void{

this.history.push(this.originator.memento);

}

restore():void{

this.originator.memento=this.history.shift();

}

}

Page 609: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InsomeimplementationsofMementoPattern,agetStatemethodisprovidedforinstancesofOriginatortoreadstatefromamemento.ButtopreventclassesotherthanOriginatorfromaccessingthestateproperty,itmayrelyonlanguagefeatureslikeafriendmodifiertorestricttheaccess(whichisnotyetavailableinTypeScript).

Page 610: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesMementoPatternmakesiteasierforacaretakertomanagethestatesoforiginatorsanditbecomespossibletoextendstateretrievingandrestoring.However,aperfectimplementationthatsealseverythingmightrelyonlanguagefeaturesaswe'vementionedbefore.Usingmementoscouldalsobringaperformancecostastheyusuallycontainredundantinformationintradeofstability.

Page 611: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IteratorPatternIteratorPatternprovidesauniversalinterfaceforaccessinginternalelementsofanaggregatewithoutexposingtheunderlyingdatastructure.Atypicaliteratorcontainsthefollowingmethodsorgetters:

first():movesthecursortothefirstelementintheaggregatesnext():movesthecursortothenextelementend:agetterthatreturnsaBooleanindicateswhetherthecursorisattheenditem:agetterthatreturnstheelementatthepositionofthecurrentcursorindex:agetterthatreturnstheindexoftheelementatthecurrentcursor

Iteratorsforaggregateswithdifferentinterfacesorunderlyingstructuresusuallyendwithdifferentimplementationsasshowninthefollowingfigure:

Thoughtheclientdoesnothavetoworryaboutthestructureofanaggregate,aniteratorwouldcertainlyneedto.Assumingwehaveeverythingweneedtobuildaniterator,therecouldbeavarietyofwaysforcreatingone.Thefactorymethodiswidelyusedwhencreatingiterators,orafactorygetterifnoparameterisrequired.

StartingwithES6,syntaxsugarfor...ofisaddedandworksforallobjectswithproperty

Page 612: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Symbol.iterator.Thismakesiteveneasierandmorecomfortablefordeveloperstoworkwithcustomizedlistsandotherclassesthatcanbeiterated.

Page 613: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofIteratorPatterninclude:

Iterator:AbstractListIterator

Definestheuniversaliteratorinterfacethatisgoingtotransversedifferentaggregates.Concreteiterator:ListIterator,SkipListIteratorandReversedListIterator

Implementsspecificiteratorthattransversesandkeepstrackofaspecificaggregate.Aggregate:AbstractList

Definesabasicinterfaceofaggregatesthatiteratorsaregoingtoworkwith.Concreateaggregate:ListandSkipList

Definesthedatastructureandfactorymethod/getterforcreatingassociatediterators.

Page 614: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeIteratorPatternprovidesaunifiedinterfacefortraversingaggregates.Inasystemthatdoesn'trelyoniterators,themainfunctionalityprovidedbyiteratorscouldbeeasilytakenoverbysimplehelpers.However,thereusabilityofthosehelperscouldbereducedasthesystemgrows.

Page 615: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationInthispart,wearegoingtoimplementastraightforwardarrayiterator,aswellasanES6iterator.

Simplearrayiterator

Let'sstartbycreatinganiteratorforaJavaScriptarray,whichshouldbeextremelyeasy.Firstly,theuniversalinterface:

interfaceIterator<T>{

first():void;

next():void;

end:boolean;

item:T;

index:number;

}

Note

PleasenoticethattheTypeScriptdeclarationforES6hasalreadydeclaredaninterfacecalledIterator.Considerputtingthecodeinthispartintoanamespaceormoduletoavoidconflicts.

Andtheimplementationofasimplearrayiteratorcouldbe:

classArrayIterator<T>implementsIterator<T>{

index=0;

constructor(

publicarray:T[]

){}

first():void{

this.index=0;

}

next():void{

this.index++;

}

getend():boolean{

returnthis.index>=this.array.length;

}

getitem():T{

returnthis.array[this.index];

}

}

NowweneedtoextendtheprototypeofnativeArraytoaddaniteratorgetter:

Object.defineProperty(Array.prototype,'iterator',{

Page 616: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

get(){

returnnewArrayIterator(this);

}

});

TomakeiteratoravalidpropertyoftheArrayinstance,wealsoneedtoextendtheinterfaceofArray:

interfaceArray<T>{

iterator:IteratorPattern.Iterator<T>;

}

Note

Thisshouldbewrittenoutsidethenamespaceundertheglobalscope.Orifyouareinamoduleorambientmodule,youmightwanttotrydeclareglobal{...}foraddingnewpropertiestoexistingglobalinterfaces.

ES6iterator

ES6providessyntaxsugarfor...ofandotherhelpersforiterableobjects,namelytheobjectsthathaveimplementedtheIterableinterfaceofthefollowing:

interfaceIteratorResult<T>{

done:boolean;

value:T;

}

interfaceIterator<T>{

next(value?:any):IteratorResult<T>;

return?(value?:any):IteratorResult<T>;

throw?(e?:any):IteratorResult<T>;

}

interfaceIterable<T>{

[Symbol.iterator]():Iterator<T>;

}

Assumewehaveaclasswiththefollowingstructure:

classSomeData<T>{

array:T[];

}

Andwewouldliketomakeititerable.Morespecifically,wewouldliketomakeititeratesreversely.AstheIterableinterfacesuggests,wejustneedtoaddamethodwithaspecialnameSymbol.iteratorforcreatinganIterator.Let'scalltheiteratorSomeIterator:

classSomeIterator<T>implementsIterator<T>{

index:number;

constructor(

publicarray:T[]

Page 617: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

){

this.index=array.length-1;

}

next():IteratorResult<T>{

if(this.index<=this.array.length){

return{

value:undefined,

done:true

};

}else{

return{

value:this.array[this.index--],

done:false

}

}

}

}

Andthendefinetheiteratormethod:

classSomeData<T>{

array:T[];

[Symbol.iterator](){

returnnewSomeIterator<T>(this.array);

}

}

NowwewouldhaveSomeDatathatworkswithfor...of.

Note

Iteratorsalsoplaywellwithgenerators;seethefollowinglinkformoreexamples:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols.

Page 618: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesIteratorPatterndecouplesiterationusagefromthedatastructurethatisbeingiterated.Thedirectbenefitofthisisenablinganinterchangeabledataclassthatmayhavecompletelydifferentinternalstructures,likeanarrayandbinarytree.Also,onedatastructurecanbeiteratedviadifferentiteratorswithdifferenttraversalmechanismsandresultsindifferentordersandefficiencies.

Aunifiediteratorinterfaceinonesystemcouldalsohelpthedeveloperfrombeingconfusedwhenfacingdifferentaggregates.Aswementionedpreviously,somelanguagelikeyourbelovedJavaScriptprovidesalanguagelevelabstractionforiteratorsandmakeslifeeveneasier.

Page 619: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MediatorPatternTheconnectionsbetweenUIcomponentsandrelatedobjectscouldbeextremelycomplex.Object-orientedprogrammingdistributesfunctionalitiesamongobjects.Thismakescodingeasierwithcleanerandmoreintuitivelogic;however,itdoesnotensurethereusabilityandsometimesmakesitdifficulttounderstandifyoulookatthecodeagainaftersomedays(youmaystillunderstandeverysingleoperationbutwouldbeconfusedabouttheinterconnectionsifthenetworkbecomesreallyintricate).

Considerapageforeditinguserprofile.Therearestandaloneinputslikenicknameandtagline,aswellasinputsthatarerelatedtoeachother.Takinglocationselectionforexample,therecouldeasilybeatree-levellocationandtheoptionsavailableinlowerlevelsaredeterminedbytheselectionofhigherlevels.However,ifthoseobjectsaremanageddirectlybyasinglehugecontroller,itwillresultinapagethathaslimitedreusability.Thecodeformedunderthissituationwouldalsotendtohaveahierarchythat'slesscleanforpeopletounderstand.

MediatorPatterntriestosolvethisproblembyseparatingcouplingelementsandobjectsasgroups,andaddingadirectorbetweenagroupofelementsandotherobjectsasshowninthefollowingfigure:

Thoseobjectsformamediatorwiththeircolleaguesthatcaninteractwithotherobjectsasasingleobject.Withproperencapsulation,themediatorwillhavebetterreusabilityasithasjusttherightsizeandproperlydividedfunctionality.Intheworldofwebfrontenddevelopment,thereareconceptsorimplementationsthatfitMediatorPatternwell,likeWebComponentandReact.

Page 620: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofMediatorPatterninclude:

Mediator:

Usually,theabstractionorskeletonpredefinedbyaframework.Definestheinterfacethatcolleaguesinamediatorcommunicatethrough.Concretemediator:LocationPicker

Managesthecolleaguesandmakesthemcooperate,providingahigherlevelinterfaceforobjectsoutside.Colleagueclasses:CountryInput,ProvinceInput,CityInput

Definesreferencestotheirmediatorandnotifiesitschangestothemediatorandacceptsmodificationsissuedbythemediator.

Page 621: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeMediatorPatterncouldconnectmanypartsofaproject,butdoesnothavedirectorenormousimpactontheoutline.Mostofthecreditisgivenbecauseofincreasedusabilityandcleanerinterconnectionsintroducedbymediators.However,alongwithaniceoverallarchitecture,MediatorPatterncanhelpalotwithrefinedcodequality,andmaketheprojecteasiertomaintain.

Page 622: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationUsinglibrarieslikeReactwouldmakeitveryeasytoimplementMediatorPattern,butfornow,wearegoingwitharelativelyprimitivewayandhandlechangesbyhand.Let'sthinkabouttheresultwewantforaLocationPickerwe'vediscussed,andhopefully,itincludescountry,provinceandcityfields:

interfaceLocationResult{

country:string;

province:string;

city:string;

}

AndnowwecansketchtheoverallstructureofclassLocationPicker:

classLocationPicker{

$country=$(document.createElement('select'));

$province=$(document.createElement('select'));

$city=$(document.createElement('select'));

$element=$(document.createElement('div'))

.append(this.$country)

.append(this.$province)

.append(this.$city);

getvalue():LocationResult{

return{

country:this.$country.val(),

province:this.$province.val(),

city:this.$city.val()

};

}

}

Beforewecantellhowthecolleaguesaregoingtocooperate,wewouldliketoaddahelpermethodsetOptionsforupdatingoptionsinaselectelement:

privatestaticsetOptions(

$select:JQuery,

values:string[]

):void{

$select.empty();

let$options=values.map(value=>{

return$(document.createElement('option'))

.text(value)

.val(value);

});

$select.append($options);

}

Ipersonallytendtohavemethodsthatdonotdependonaspecificinstancestaticmethodsand

Page 623: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thisappliestomethodsgetCountries,getProvincesByCountry,andgetCitiesByCountryAndProvincethatsimplyreturnalistbytheinformationgivenasfunctionarguments(thoughwearenotgoingtoactuallyimplementthatpart):

privatestaticgetCountries():string[]{

return['-'].concat([/*countries*/]);

}

privatestaticgetProvincesByCountry(country:string):string[]{

return['-'].concat([/*provinces*/]);

}

privatestaticgetCitiesByCountryAndProvince(

country:string,

province:string

):string[]{

return['-'].concat([/*cities*/]);

}

Nowwemayaddmethodsforupdatingoptionsintheselectelements:

updateProvinceOptions():void{

letcountry:string=this.$country.val();

letprovinces=LocationPicker.getProvincesByCountry(country);

LocationPicker.setOptions(this.$province,provinces);

this.$city.val('-');

}

updateCityOptions():void{

letcountry:string=this.$country.val();

letprovince:string=this.$province.val();

letcities=LocationPicker

.getCitiesByCountryAndProvince(country,province);

LocationPicker.setOptions(this.$city,cities);

}

Finally,weavethosecolleaguestogetherandaddlistenerstothechangeevents:

constructor(){

LocationPicker

.setOptions(this.$country,LocationPicker.getCountries());

LocationPicker.setOptions(this.$province,['-']);

LocationPicker.setOptions(this.$city,['-']);

this.$country.change(()=>{

this.updateProvinceOptions();

});

this.$province.change(()=>{

this.updateCityOptions();

});

}

Page 624: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesMediatorPattern,likemanyotherdesignpatterns,downgradesalevel-100problemintotwolevel-10problemsandsolvesthemseparately.Awell-designedmediatorusuallyhasapropersizeandusuallytendstobereusedinthefuture.Forexample,wemightnotwanttoputnicknameinputtogetherwiththecountry,province,andcityinputsasthiscombinationdoesn'ttendtooccurinothersituations(whichmeanstheyarenotstronglyrelated).

Astheprojectevolves,amediatormaygrowtoasizethat'snotefficientanymore.Soaproperlydesignedmediatorshouldalsotakethedimensionoftimeintoconsideration.

Page 625: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wetalkedaboutsomecommonbehavioralpatternsfordifferentscopesanddifferentscenarios.ChainofResponsibilityPatternandCommandPatterncanapplytoarelativelywiderangeofscopes,whileotherpatternsmentionedinthischapterusuallycaremoreaboutthescopewithobjectsandclassesdirectlyrelated.

Behavioralpatternswe'vetalkedaboutinthischapterarelesslikeeachothercomparedtocreationalpatternsandstructuralpatternswepreviouslywalkedthrough.Someofthebehavioralpatternscouldcompetewithothers,butmanyofthemcouldcooperate.Forexample,wetalkedaboutCommandPatternwithMementoPatterntoimplementundosupport.Manyothersmaycooperateinparallelanddotheirownpart.

Inthenextchapter,we'llcontinuetalkingaboutotherbehavioraldesignpatternsthatareusefulandwidelyused.

Page 626: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter6.BehavioralDesignPatterns:ContinuousInthepreviouschapter,we'vealreadytalkedaboutsomeofthebehavioraldesignpatterns.We'llbecontinuingwithmorepatternsinthiscategoryinthischapter,including:StrategyPattern,StatePattern,TemplateMethodPattern,ObserverPattern,andVisitorPattern.

Manyofthesepatternssharethesameidea:unifytheshapeandvarythedetails.Hereisaquickoverview:

StrategyPatternandTemplatePattern:DefinesthesameoutlineofalgorithmsStatePattern:ProvidesdifferentbehaviorforobjectsindifferentstateswiththesameinterfaceObserverPattern:ProvidesaunifiedprocessofhandlingsubjectchangesandnotifyingobserversVisitorPattern:DoessimilarjobsasStrategyPatternsometimes,butavoidsanovercomplexinterfacethatmightberequiredforStrategyPatterntohandleobjectsinmanydifferenttypes

Patternsthatwillbediscussedinthischaptercouldbeappliedindifferentscopesjustasmanypatternsinothercategories.

Page 627: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StrategyPatternIt'scommonthataprogramhassimilaroutlinesforprocessingdifferenttargetswithdifferentdetailedalgorithms.StrategyPatternencapsulatesthosealgorithmsandmakestheminterchangeablewithinthesharedoutline.

Considerconflictingmergingprocessesofdatasynchronization,whichwetalkedaboutinChapter2,TheChallengeofIncreasingComplexity.Beforerefactoring,thecodewaslikethis:

if(type==='value'){

//...

}elseif(type==='increment'){

//...

}elseif(type==='set'){

//...

}

Butlaterwefoundoutthatwecouldactuallyextractthesameoutlinesfromdifferentphasesofthesynchronizationprocess,andencapsulatethemasdifferentstrategies.Afterrefactoring,theoutlineofthecodebecameasfollows:

letstrategy=strategies[type];

strategy.operation();

WegetalotofwaystocomposeandorganizethosestrategyobjectsorclassessometimesinJavaScript.ApossiblestructureforStrategyPatterncouldbe:

Inthisstructure,theclientisresponsibleforfetchingspecificstrategiesfromthetableand

Page 628: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

applyingoperationsofthecurrentphase.

Anotherstructureisusingcontextualobjectsandlettingthemcontroltheirownstrategies:

Thustheclientneedsonlytolinkaspecificcontextwiththecorrespondingstrategy.

Page 629: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsWe'vementionedtwopossiblestructuresforStrategyPattern,solet'sdiscusstheparticipantsseparately.Forthefirststructure,theparticipantsincludethefollowing:

Strategy

Definestheinterfaceofstrategyobjectsorclasses.Concretestrategy:ConcreteStrategyAandConcreteStrategyB

ImplementsconcretestrategyoperationsdefinedbytheStrategyinterface.Strategymanager:Strategies

Definesadatastructuretomanagestrategyobjects.Intheexample,it'sjustasimplehashtablethatusesdatatypenamesaskeysandstrategyobjectsasvalues.Itcouldbemorecomplexondemand:forexample,withmatchingpatternsorconditions.Target

Thetargettoapplyalgorithmsdefinedinstrategyobjects.Client

Makestargetsandstrategiescooperate.

Theparticipantsofthesecondstructureincludethefollowing:

Strategyandconcretestrategy

Thesameasintheprecedingsection.Context

Definesareferencetothestrategyobjectapplied.Providesrelatedmethodsorpropertygettersforclientstooperate.Client

Managescontextobjects.

Page 630: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeStrategyPatternisusuallyappliedtoscopeswithsmallormediumsizes.Itprovidesawaytoencapsulatealgorithmsandmakesthosealgorithmseasiertomanageunderthesameoutline.StrategyPatterncanalsobethecoreofanentiresolutionsometimes,andagoodexampleisthesynchronizationimplementationwe'vebeenplayingwith.Inthiscase,StrategyPatternbuildsthebridgeofpluginsandmakesthesystemextendable.Butmostofthetime,thefundamentalworkdonebyStrategyPatternisdecouplingconcretestrategies,contexts,ortargets.

Page 631: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationTheimplementationstartswithdefiningtheinterfacesofobjectswe'llbeplayingwith.Wehavetwotargettypesinstringliteraltype'a'and'b'.Targetsoftype'a'havearesultpropertywithtypestring,whiletargetsoftype'b'haveavaluepropertywithtypenumber.

Theinterfaceswe'llhavelook,arelike:

typeTargetType='a'|'b';

interfaceTarget{

type:TargetType;

}

interfaceTargetAextendsTarget{

type:'a';

result:string;

}

interfaceTargetBextendsTarget{

type:'b';

value:number;

}

interfaceStrategy<TTargetextendsTarget>{

operationX(target:TTarget):void;

operationY(target:TTarget):void;

}

Nowwe'lldefinetheconcretestrategyobjectswithoutaconstructor:

letstrategyA:Strategy<TargetA>={

operationX(target){

target.result=target.result+target.result;

},

operationY(target){

target.result=target

.result

.substr(Math.floor(target.result.length/2));

}

};

letstrategyB:Strategy<TargetB>={

operationX(target){

target.value=target.value*2;

},

operationY(target){

target.value=Math.floor(target.value/2);

}

};

Tomakeiteasierforaclienttofetchthosestrategies,we'llputthemintoahashtable:

letstrategies:{

Page 632: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

[type:string]:Strategy<Target>

}={

a:strategyA,

b:strategyB

};

Andnowwecanmakethemworkwithtargetsindifferenttypes:

lettargets:Target[]=[

{type:'a'},

{type:'a'},

{type:'b'}

];

for(lettargetoftargets){

letstrategy=strategies[target.type];

strategy.operationX(target);

strategy.operationY(target);

}

Page 633: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesStrategyPatternmakestheforeseeableadditionofalgorithmsforcontextsortargetsundernewcategorieseasier.Italsomakestheoutlineofaprocessevencleanerbyhidingtrivialbranchesofbehaviorsselection.

However,theabstractionofalgorithmsdefinedbytheStrategyinterfacemaykeepgrowingwhilewearetryingtoaddmorestrategiesandsatisfytheirrequirementsofparameters.ThiscouldbeaproblemforaStrategyPatternwithclientsthataremanagingtargetsandstrategies.Butfortheotherstructureswhichthereferencesofstrategyobjectsarestoredbycontextsthemselves,wecanmanagetotrade-offtheinterchangeability.ThiswouldresultinVisitorPattern,whichwearegoingtotalkaboutlaterinthischapter.

Andaswe'vementionedbefore,StrategyPatterncanalsoprovidenotableextensibilityifanextendablestrategymanagerisavailableortheclientofcontextsisdesignedto.

Page 634: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StatePatternIt'spossibleforsomeobjectstobehavecompletelydifferentlywhentheyareindifferentstates.Let'sthinkaboutaneasyexamplefirst.Considerrenderingandinteractingwithacustombuttonintwostates:enabledanddisabled.Whenthebuttonisenabled,itlightsupandchangesitsstyletoactiveonamousehover,andofcourse,ithandlesclicks;whendisabled,itdimsandnolongercaresaboutmouseevents.

Wemaythinkofanabstractionwithtwooperations:render(withaparameterthatindicateswhetherthemouseishovering)andclick;alongwithtwostates:enabledanddisabled.Wecanevendividedeeperandhavestateactive,butthatwon'tbenecessaryinourcase.

AndnowwecanhaveStateEnabledwithbothrenderandclickmethodsimplemented,whilehavingStateDisabledwithonlyrendermethodimplementedbecauseitdoesnotcareaboutthehoverparameter.Inthisexample,weareexpectingeverymethodofthestatesbeingcallable.SowecanhavetheabstractclassStatewithemptyrenderandclickmethods.

Page 635: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofStatePatternincludethefollowing:

State

Definestheinterfaceofstateobjectsthatarebeingswitchedtointernally.Concretestate:StateEnabledandStateDisabled

ImplementstheStateinterfacewithbehaviorcorrespondingtoaspecificstateofthecontext.Mayhaveanoptionalreferencebacktoitscontext.Context

Managesreferencestodifferentstates,andmakesoperationsdefinedontheactiveone.

Page 636: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeStatePatternusuallyappliestothecodeofscopeswiththesizeofafeature.Itdoesnotspecifywhomtotransferthestateofcontext:itcouldbeeitherthecontextitself,thestatemethods,orcodethatcontrolscontext.

Page 637: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationStartwiththeStateinterface(itcouldalsobeanabstractclassifthereareoperationsorlogictoshare):

interfaceState{

render(hover:boolean):void;

click():void;

}

WiththeStateinterfacedefined,wecanmovetoContextandsketchitsoutline:

classContext{

$element:JQuery;

state:State;

privaterender(hover:boolean):void{

this.state.render(hover);

}

privateclick():void{

this.state.click();

}

onclick():void{

console.log('Iamclicked.');

}

}

Nowwearegoingtohavethetwostates,StateEnabledandStateDisabledimplemented.First,let'saddressStateEnabled,itcaresabouthoverstatusandhandlesclickevent:

classStateEnabledimplementsState{

constructor(

publiccontext:Context

){}

render(hover:boolean):void{

this

.context

.$element

.removeClass('disabled')

.toggleClass('hover',hover);

}

click():void{

this.context.onclick();

}

}

Next,forStateDisableditjustignoreshoverparameteranddoesnothingwhenclickeventemits:

Page 638: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

classStateDisabledimplementsState{

constructor(

publiccontext:Context

){}

render():void{

this

.context

.$element

.addClass('disabled')

.removeClass('hover');

}

click():void{

//Donothing.

}

}

Nowwehaveclassesofstatesenabledanddisabledready.Astheinstancesofthoseclassesareassociatedwiththecontext,weneedtoinitializeeverystatewhenanewContextisinitiated:

classContext{

...

privatestateEnabled=newStateEnabled(this);

privatestateDisabled=newStateDisabled(this);

state:State=this.stateEnabled;

...

}

Itispossibletouseflyweightsbypassingcontextinwheninvokingeveryoperationontheactivestateaswell.

Nowlet'sfinishtheContextbylisteningtoandforwardingproperevents:

constructor(){

this

.$element

.hover(

()=>this.render(true),

()=>this.render(false)

)

.click(()=>this.click());

this.render(false);

}

Page 639: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesStatePatternreducesconditionalbranchesinpotentiallymultiplemethodsofcontextobjects.Asatrade-off,extrastateobjectsareintroduced,thoughitusuallywon'tbeabigproblem.

ThecontextobjectinStatePatternusuallydelegatesoperationsandforwardsthemtothecurrentstateobject.Thusoperationsdefinedbyaconcretestatemayhaveaccesstothecontextitself.Thismakesreusingstateobjectspossiblewithflyweights.

Page 640: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TemplateMethodPatternWhenwearetalkingaboutsubclassingorinheriting,thebuildingisusuallybuiltfromthebottomup.Subclassesinheritthebasisandthenprovidemore.However,itcouldbeusefultoreversethestructuresometimesaswell.

ConsiderStrategyPatternwhichdefinestheoutlineofaprocessandhasinterchangeablealgorithmsasstrategies.Ifweapplythisstructureunderthehierarchyofclasses,wewillhaveTemplateMethodPattern.

Atemplatemethodisanabstractmethod(optionallywithdefaultimplementation)andactsasaplaceholderundertheoutlineofalargerprocess.Subclassesoverrideorimplementrelatedmethodstomodifyorcompletethebehaviors.ImagingtheskeletonofaTextReader,weareexpectingitssubclassestohandletextfilesfromdifferentstoragemedia,detectdifferentencodingsandreadallthetext.Wemayconsiderastructurelikethis:

TheTextReaderinthisexamplehasamethodreadAllTextthatreadsalltextfromaresourcebytwosteps:readingallbytesfromtheresource(readAllBytes),andthendecodingthosebyteswithcertainencoding(decodeBytes).

Thestructurealsosuggeststhepossibilityofsharingimplementationsamongconcreteclassesthatimplementtemplatemethods.WemaycreateanabstractclassAsciiTextReaderthatextendsTextReaderandimplementsmethoddecodeBytes.AndbuildconcreteclassesFileAsciiTextReaderandHttpAsciiTextReaderthatextendAsciiTextReaderandimplementmethodreadAllBytestohandleresourcesondifferentstoragemedia.

Page 641: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofTemplateMethodPatternincludethefollowing:

Abstractclass:TextReader

Definesthesignaturesoftemplatemethods,aswellastheoutlineofalgorithmsthatweaveeverythingtogether.Concreteclasses:AsciiTextReader,FileAsciiTextReaderandHttpAsciiTextReader

Implementstemplatemethodsdefinedinabstractclasses.TypicalconcreteclassesareFileAsciiTextReaderandHttpAsciiTextReaderinthisexample.However,comparedtobeingabstract,definingtheoutlineofalgorithmsweighsmoreinthecategorization.

Page 642: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeTemplateMethodPatternisusuallyappliedinarelativelysmallscope.Itprovidesanextendablewaytoimplementfeaturesandavoidredundancyfromtheupperstructureofaseriesofalgorithms.

Page 643: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationTherearetwolevelsoftheinheritinghierarchy:theAsciiTextReaderwillsubclassTextReaderasanotherabstractclass.ItimplementsmethoddecodeBytesbutleavesreadAllBytestoitssubclasses.StartingwiththeTextReader:

abstractclassTextReader{

asyncreadAllText():Promise<string>{

letbytes=awaitthis.readAllBytes();

lettext=this.decodeBytes(bytes);

returntext;

}

abstractasyncreadAllBytes():Promise<Buffer>;

abstractdecodeBytes(bytes:Buffer):string;

}

Tip

WeareusingPromiseswithasyncandawaitwhicharecomingtoECMAScriptnext.Pleaserefertothefollowinglinksformoreinformation:https://github.com/Microsoft/TypeScript/issues/1664https://tc39.github.io/ecmascript-asyncawait/

Andnowlet'ssubclassTextReaderasAsciiTextReaderwhichstillremainsabstract:

abstractclassAsciiTextReaderextendsTextReader{

decodeBytes(bytes:Buffer):string{

returnbytes.toString('ascii');

}

}

ForFileAsciiTextReader,we'llneedtoimportfilesystem(fs)moduleofNode.jstoperformfilereading:

import*asFSfrom'fs';

classFileAsciiTextReaderextendsAsciiTextReader{

constructor(

publicpath:string

){

super();

}

asyncreadAllBytes():Promise<Buffer>{

returnnewPromise<Buffer>((resolve,reject)=>{

FS.readFile(this.path,(error,bytes)=>{

if(error){

reject(error);

}else{

resolve(bytes);

Page 644: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

});

});

}

}

ForHttpAsciiTextReader,wearegoingtouseapopularpackagerequesttosendHTTPrequests:

import*asrequestfrom'request';

classHttpAsciiTextReaderextendsAsciiTextReader{

constructor(

publicurl:string

){

super();

}

asyncreadAllBytes():Promise<Buffer>{

returnnewPromise<Buffer>((resolve,reject)=>{

request(this.url,{

encoding:null

},(error,bytes,body)=>{

if(error){

reject(error);

}else{

resolve(body);

}

});

});

}

}

Tip

BothconcretereaderimplementationspassresolverfunctionstothePromiseconstructorforconvertingasynchronousNode.jsstylecallbackstoPromises.Formoreinformation,readmoreaboutthePromiseconstructor:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise.

Page 645: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesComparedtoStrategyPattern,TemplateMethodPatternprovidesconvenienceforbuildingobjectswiththesameoutlineofalgorithmsoutsideoftheexistingsystem.ThismakesTemplateMethodPatternausefulwaytobuildtoolingclassesinsteadoffixedprocessesbuilt-in.

ButTemplateMethodPatternhaslessruntimeflexibilityasitdoesnothaveamanager.Italsoreliesontheclientwho'susingthoseobjectstodothework.AndastheimplementationofTemplateMethodPatternreliesonsubclassing,itcouldeasilyresultinhierarchiesthathaveasimilarcodeondifferentbranches.Thoughthiscouldbeoptimizedbyusingtechniqueslikemixin.

Page 646: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ObserverPatternObserverPatternisanimportantPatternbackedbyanimportantideainsoftwareengineering.AnditisusuallyakeypartofMVCarchitectureanditsvariantsaswell.

IfyouhaveeverwrittenanapplicationwitharichuserinterfacewithoutaframeworklikeAngularorasolutionwithReact,youmightprobablyhavestruggledwithchangingclassnamesandotherpropertiesofUIelements.Morespecifically,thecodethatcontrolsthosepropertiesofthesamegroupofelementslieseverybranchrelatedtotheelementsinrelatedeventlisteners,justtokeeptheelementsbeingcorrectlyupdated.

Considera"Do"buttonofwhichthedisabledpropertyshouldbedeterminedbythestatusofaWebSocketconnectiontoaserverandwhetherthecurrentlyactiveitemisdone.Everytimethestatusofeithertheconnectionortheactiveitemgetsupdated,we'llneedtoupdatethebuttoncorrespondingly.Themost"handy"waycouldbetwosomewhatidenticalgroupsofcodebeingputintwoeventlisteners.Butinthisway,theamountofsimilarcodewouldjustkeepgrowingasmorerelevantobjectsgetinvolved.

Theprobleminthis"Do"buttonexampleisthat,thebehaviorofcodethat'scontrollingthebuttonisdrivenbyprimitiveevents.Theheavyloadofmanagingtheconnectionsandbehaviorsamongdifferenteventsisdirectlytakenbythedeveloperwho'swritingthatcode.Andunfortunately,thecomplexityinthiscase,growsexponentially,whichmeansitcouldeasilyexceedourbraincapacity.Writingcodethiswaymightresultinmorebugsandmakemaintainingmuchlikelytointroducenewbugs.

Butthebeautifulthingis,wecanfindthefactorsthatmultiplyandoutputthedesiredresult,andthereferencefordividingthosefactorsaregroupsofrelatedstates.Stillspeakingofthe"Do"buttonexample,whatthebuttoncaresaboutis:connectionstatusandtheactiveitemstatus(assumingtheyarebooleansconnectedandloaded).Wecanhavethecodewrittenastwoparts:onepartthatchangesthosestates,andanotherpartthatupdatesthebutton:

letbutton=document.getElementById('do-button');

letconnected=false;

letloaded=false;

functionupdateButton(){

letdisabled=!connected&&!loaded;

button.disabled=disabled;

}

connection.on('statuschange',event=>{

connected=event.connected;

updateButton();

});

activeItem.on('statuschange',event=>{

loaded=event.loaded;

Page 647: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

updateButton();

});

TheprecedingsamplecodealreadyhastheembryoofObserverPattern:thesubjects(statesconnectedandloaded)andtheobserver(updateButtonfunction),thoughwestillneedtocallupdateButtonmanuallyeverytimeanyrelatedstatechanges.Animprovedstructurecouldlooklikethefollowingfigure:

Butjustliketheexamplewe'vebeentalkingabout,observersinmanysituationscareaboutmorethanonestate.Itcouldbelesssatisfyingtohavesubjectsattachobserversseparately.

Asolutiontothiscouldbemulti-statesubjects,toachievethat,wecanformacompositesubjectthatcontainssub-subjects.Ifasubjectreceivesanotifycall,itwakesupitsobserversandatthesametimenotifiesitsparent.Thustheobservercanattachonecompositesubjectfornotificationsofchangesthathappentomultiplestates.

However,theprocessofcreatingthecompositeitselfcouldstillbeannoying.Indynamic

Page 648: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

programminglanguageslikeJavaScript,wemayhaveastatemanagerthatcontainsspecificstateshandlingnotificationsandattachingobserversdirectlywithimplicitcreationsofsubjects:

letstateManager=newStateManager({

connected:false,

loaded:false,

foo:'abc',

bar:123

});

stateManager.on(['connected','loaded'],()=>{

letdisabled=

!stateManager.connected&&!stateManager.loaded;

button.disabled=disabled;

});

Note

InmanyMV*frameworks,thestatestobeobservedareanalyzedautomaticallyfromrelatedexpressionsbybuilt-inparsersorsimilarmechanisms.

Andnowthestructuregetsevensimpler:

Page 649: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsWe'vetalkedaboutthebasicstructureofObserverPatternwithsubjectsandobservers,andavariantwithimplicitsubjects.Theparticipantsofthebasicstructureincludethefollowing:

Subject

Subjecttobeobserved.Definesmethodstoattachornotifyobservers.Asubjectcouldalsobeacompositethatcontainssub-subjects,whichallowsmultiplestatestobeobservedwiththesameinterface.Concretesubject:ConnectedSubjectandLoadedSubject

Containsstaterelatedtothesubject,andimplementsmethodsorpropertiestogetandsettheirstate.Observer

Definestheinterfaceofanobjectthatreactswhenanobservationnotifies.InJavaScript,itcouldalsobeaninterface(orsignature)ofafunction.Concreteobserver:DoButtonObserver

Definestheactionthatreactstothenotificationsofsubjectsbeingobserved.Couldbeacallbackfunctionthatmatchesthesignaturedefined.

Inthevariantversion,theparticipantsincludethefollowing:

Statemanager

Managesacomplex,possiblymulti-levelstateobjectcontainingmultiplestates.Definestheinterfacetoattachobserverswithsubjects,andnotifiesthoseobserverswhenasubjectchanges.Concretesubject

Keystospecificstates.Forexample,string"connected"mayrepresentstatestateManager.connected,whilestring"foo.bar"mayrepresentstatestateManager.foo.bar.

Observerandconcreteobserverarebasicallythesameasdescribedintheformerstructure.Butobserversarenownotifiedbythestatemanagerinsteadofsubjectobjects.

Page 650: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeObserverPatternisapatternthatmayeasilystructurehalfoftheproject.InMV*architectures,ObserverPatterncandecoupletheviewfrombusinesslogic.Theconceptofviewcanbeappliedtootherscenariosrelatedtodisplayinginformationaswell.

Page 651: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationBothofthestructureswe'vementionedshouldnotbehardtoimplement,thoughmoredetailsshouldbeputintoconsiderationforproductioncode.We'llgowiththesecondimplementationthathasacentralstatemanager.

Note

Tosimplifytheimplementation,wewillusegetandsetmethodstoaccessspecificstatesbytheirkeys.Butmanyframeworksavailablemighthandlethosethroughgettersandsetters,orothermechanisms.

Note

TolearnabouthowframeworkslikeAngularhandlestateschanging,pleasereadtheirdocumentationorsourcecodeifnecessary.

WearegoingtohaveStateManagerinheritEventEmitter,sowedon'tneedtocaremuchaboutissueslikemultiplelisteners.Butasweareacceptingmultiplestatekeysassubjects,anoverloadtomethodonwillbeadded.ThustheoutlineofStateManagerwouldbeasfollows:

typeObserver=()=>void;

classStateManagerextendsEventEmitter{

constructor(

privatestate:any

){

super();

}

set(key:string,value:any):void{}

get(key:string):any{}

on(state:string,listener:Observer):this;

on(states:string[],listener:Observer):this;

on(states:string|string[],listener:Observer):this{}

}

Tip

Youmighthavenoticedthatmethodonhasthereturntypethis,whichmaykeepreferringtothetypeofcurrentinstance.Typethisisveryhelpfulforchainingmethods.

Thekeyswillbe"foo"and"foo.bar",weneedtosplitakeyasseparateidentifiersforaccessingthevaluefromthestateobject.Let'shaveaprivate_getmethodthattakesanarrayofidentifiersasinput:

private_get(identifiers:string[]):any{

letnode=this.state;

Page 652: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

for(letidentifierofidentifiers){

node=node[identifier];

}

returnnode;

}

Nowwecanimplementmethodgetupon_get:

get(key:string):any{

letidentifiers=key.split('.');

returnthis._get(identifiers);

}

Formethodset,wecangettheparentobjectofthelastidentifierofpropertytobeset,sothingsworklikethis:

set(key:string,value:any):void{

letidentifiers=key.split('.');

letlastIndex=identifiers.length-1;

letnode=this._get(identifiers.slice(0,lastIndex));

node[identifiers[lastIndex]]=value;

}

Butthere'sonemorething,weneedtonotifyobserversthatareobservingacertainsubject:

set(key:string,value:any):void{

letidentifiers=key.split('.');

letlastIndex=identifiers.length-1;

letnode=this._get(identifiers.slice(0,lastIndex));

node[identifiers[lastIndex]]=value;

for(leti=identifiers.length;i>0;i--){

letkey=identifiers.slice(0,i).join('.');

this.emit(key);

}

}

Whenwe'redonewiththenotifyingpart,let'saddanoverloadformethodontosupportmultiplekeys:

on(state:string,listener:Observer):this;

on(states:string[],listener:Observer):this;

on(states:string|string[],listener:Observer):this{

if(typeofstates==='string'){

super.on(states,listener);

}else{

for(letstateofstates){

super.on(state,listener);

}

}

Page 653: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnthis;

}

Problemsolved.Nowwehaveastatemanagerthatwillworkforsimplescenarios.

Page 654: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesObserverPatterndecouplessubjectswithobservers.Whileanobservermaybeobservingmultiplestatesinsubjectsatthesametime,itusuallydoesnotcareaboutwhichstatetriggersthenotification.Asaresult,theobservermaymakeunnecessaryupdatesthatactuallydonothingto-forexample-theview.

However,theimpactonperformancecouldbenegligiblemostofthetime,notevenneedtomentionthebenefitsitbrings.

Bysplittingviewandlogicapart,ObserverPatternmayreducepossiblebranchessignificantly.Thiswillhelpeliminatebugscausedatthecouplingpartbetweenviewandlogic.Thus,byproperlyapplyingObserverPattern,theprojectwillbemademuchmorerobustandeasiertomaintain.

However,therearesomedetailswestillneedcareabout:

1. Theobserverthatupdatesthestatecouldcausecircularinvocation.2. Formorecomplexdatastructureslikecollections,itmightbeexpensivetore-render

everything.Observersinthisscenariomayneedmoreinformationaboutthechangetoonlyperformnecessaryupdates.ViewimplementationslikeReactdothisinanotherway;theyintroduceaconceptcalledVirtualDOM.ByupdatinganddiffingthevirtualDOMbeforere-renderingtheactualDOM(whichcouldusuallybethebottleneckofperformance),itprovidesarelativelygeneralsolutionfordifferentdatastructures.

Page 655: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

VisitorPatternVisitorPatternprovidesauniformedinterfaceforvisitingdifferentdataorobjectswhileallowingdetailedoperationsinconcretevisitorstovary.VisitorPatternisusuallyusedwithcomposites,anditiswidelyusedforwalkingthroughdatastructureslikeabstractsyntaxtree(AST).Buttomakeiteasierforthosewhoarenotfamiliarwithcompilerstuff,wewillprovideasimplerexample.

ConsideraDOM-liketreecontainingmultipleelementstorender:

[

Text{

content:"Hello,"

},

BoldText{

content:"TypeScript"

},

Text{

content:"!Populareditors:\n"

},

UnorderedList{

items:[

ListItem{

content:"VisualStudioCode"

},

ListItem{

content:"VisualStudio"

},

ListItem{

content:"WebStorm"

}

]

}

]

TherenderingresultinHTMLwouldlooklikethis:

WhileinMarkdown,itwouldlooklikethis:

Page 656: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

VisitorPatternallowsoperationsinthesamecategorytobecodedinthesameplace.We'llhaveconcretevisitors,HTMLVisitorandMarkdownVisitorthattaketheresponsibilitiesoftransformingdifferentnodesbyvisitingthemrespectivelyandrecursively.Thenodesbeingvisitedhaveamethodacceptforacceptingavisitortoperformthetransformation.AnoverallstructureofVisitorPatterncouldbesplitintotwoparts,thefirstpartisthevisitorabstractionanditsconcretesubclasses:

Thesecondpartistheabstractionandconcretesubclassesofnodestobevisited:

Page 657: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management
Page 658: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParticipantsTheparticipantsofVisitorPatternincludethefollowing:

Visitor:NodeVisitor

Definestheinterfaceofoperationscorrespondingtoeachelementclass.Inlanguageswithstatictypesandmethodoverloading,themethodnamescanbeunified.ButasittakesextraruntimecheckinginJavaScript,we'llusedifferentmethodnamestodistinguishthem.Theoperationmethodsareusuallynamedaftervisit,buthereweuseappendasitsmorerelatedtothecontext.Concretevisitor:HTMLVisitorandMarkdownVisitor

Implementseveryoperationoftheconcretevisitor,andhandlesinternalstatesifany.Element:Node

Definestheinterfaceoftheelementacceptingthevisitorinstance.Themethodisusuallynamedaccept,thoughhereweareusingappendToforabettermatchingwiththecontext.Elementscouldthemselvesbecompositesandpassvisitorsonwiththeirchildelements.Concreteelement:Text,BoldText,UnorderedListandListItem

Implementsacceptmethodandcallsthemethodfromthevisitorinstancecorrespondingtotheelementinstanceitself.Client:

Enumerateselementsandappliesvisitorstothem.

Page 659: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PatternscopeVisitorPatterncanformalargefeatureinsideasystem.Forsomeprogramsundercertaincategories,itmayalsoformthecorearchitecture.Forexample,BabelusesVisitorPatternforASTtransformingandapluginforBabelisactuallyavisitorthatcanvisitandtransformelementsitcaresabout.

Page 660: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationWearegoingtoimplementHTMLVisitorandMarkdownVisitorwhichmaytransformnodestotext,aswe'vetalkedabout.Startwiththeupperabstraction:

interfaceNode{

appendTo(visitor:NodeVisitor):void;

}

interfaceNodeVisitor{

appendText(text:Text):void;

appendBold(text:BoldText):void;

appendUnorderedList(list:UnorderedList):void;

appendListItem(item:ListItem):void;

}

Continuewithconcretenodesthatdosimilarthings,TextandBoldText:

classTextimplementsNode{

constructor(

publiccontent:string

){}

appendTo(visitor:NodeVisitor):void{

visitor.appendText(this);

}

}

classBoldTextimplementsNode{

constructor(

publiccontent:string

){}

appendTo(visitor:NodeVisitor):void{

visitor.appendBold(this);

}

}

Andliststuff:

classUnorderedListimplementsNode{

constructor(

publicitems:ListItem[]

){}

appendTo(visitor:NodeVisitor):void{

visitor.appendUnorderedList(this);

}

}

classListItemimplementsNode{

constructor(

publiccontent:string

){}

Page 661: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

appendTo(visitor:NodeVisitor):void{

visitor.appendListItem(this);

}

}

Nowwehavetheelementsofastructuretobevisited,we'llbegintoimplementconcretevisitors.Thosevisitorswillhaveanoutputpropertyforthetransformedstring.HTMLVisitorgoesfirst:

classHTMLVisitorimplementsNodeVisitor{

output='';

appendText(text:Text){

this.output+=text.content;

}

appendBold(text:BoldText){

this.output+=`<b>${text.content}</b>`;

}

appendUnorderedList(list:UnorderedList){

this.output+='<ul>';

for(letitemoflist.items){

item.appendTo(this);

}

this.output+='</ul>';

}

appendListItem(item:ListItem){

this.output+=`<li>${item.content}</li>`;

}

}

PayattentiontotheloopinsideappendUnorderedList,ithandlesvisitingofitsownlistitems.

AsimilarstructureappliestoMarkdownVisitor:

classMarkdownVisitorimplementsNodeVisitor{

output='';

appendText(text:Text){

this.output+=text.content;

}

appendBold(text:BoldText){

this.output+=`**${text.content}**`;

}

appendUnorderedList(list:UnorderedList){

this.output+='\n';

for(letitemoflist.items){

Page 662: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

item.appendTo(this);

}

}

appendListItem(item:ListItem){

this.output+=`-${item.content}\n`;

}

}

Nowtheinfrastructuresareready,let'screatethetree-likestructurewe'vebeenimaginingsincethebeginning:

letnodes=[

newText('Hello,'),

newBoldText('TypeScript'),

newText('!Populareditors:\n'),

newUnorderedList([

newListItem('VisualStudioCode'),

newListItem('VisualStudio'),

newListItem('WebStorm')

])

];

Andfinally,buildtheoutputswithvisitors:

lethtmlVisitor=newHTMLVisitor();

letmarkdownVisitor=newMarkdownVisitor();

for(letnodeofnodes){

node.appendTo(htmlVisitor);

node.appendTo(markdownVisitor);

}

console.log(htmlVisitor.output);

console.log(markdownVisitor.output);

Page 663: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConsequencesBothStrategyPatternandVisitorPatterncouldbeappliedtoscenariosofprocessingobjects.ButStrategyPatternreliesonclientstohandleallrelatedargumentsandcontexts,thismakesithardtocomeoutwithanexquisiteabstractioniftheexpectedbehaviorsofdifferentobjectsdifferalot.VisitorPatternsolvesthisproblembydecouplingvisitactionsandoperationstobeperformed.

Bypassingdifferentvisitors,VisitorPatterncanapplydifferentoperationstoobjectswithoutchangingothercodealthoughitusuallymeansaddingnewelementsandwouldresultinaddingrelatedoperationstoanabstractvisitorandallofitsconcretesubclasses.

VisitorsliketheNodeVisitorinthepreviousexamplemaystorestateitself(inthatexample,westoredtheoutputoftransformednodes)andmoreadvancedoperationscanbeappliedbasedonthestateaccumulated.Forexample,it'spossibletodeterminewhathasbeenappendedtotheoutput,andthuswecanapplydifferentbehaviorswiththenodecurrentlybeingvisited.

However,tocompletecertainoperations,extrapublicmethodsmayneedtobeexposedfromtheelements.

Page 664: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,we'vetalkedaboutotherbehaviordesignpatternsascomplementstotheformerchapter,includingStrategy,State,TemplateMethod,ObserverandVisitorPattern.

StrategyPatternissocommonandusefulthatitmayappearinaprojectseveraltimes,withdifferentforms.AndyoumightnotknowyouwereusingObserverPatternwithimplementationinadailyframework.

Afterwalkingthroughthosepatterns,youmightfindtherearemanyideasincommonbehindeachpattern.Itisworththinkingwhat'sbehindthemandevenlettingtheoutlinegoinyourmind.

Inthenextchapter,we'llcontinuewithsomehandypatternsrelatedtoJavaScriptandTypeScript,andimportantscenariosofthoselanguages.

Page 665: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter7.PatternsandArchitecturesinJavaScriptandTypeScriptInthepreviousfourchapters,we'vewalkedthroughcommonandclassicaldesignpatternsanddiscussedsomeoftheirvariantsinJavaScriptorTypeScript.Inthischapter,we'llcontinuewithsomearchitectureandpatternscloselyrelatedtothelanguageandtheircommonapplications.Wedon'thavemanypagestoexpandandcertainlycannotcovereverythinginasinglechapter,sopleasetakeitasanappetizerandfeelfreetoexploremore.

Manytopicsinthischapterarerelatedtoasynchronousprogramming.We'llstartwithawebarchitectureforNode.jsthat'sbasedonPromise.Thisisalargertopicthathasinterestingideasinvolved,includingabstractionsofresponsesandpermissions,aswellaserrorhandlingtips.Thenwe'lltalkabouthowtoorganizemoduleswithECMAScript(ES)modulesyntax.Andthischapterwillendwithseveralusefulasynchronoustechniques.

Overall,we'llhavethefollowingtopicscoveredinthischapter:

ArchitectureandtechniquesrelatedtoPromiseAbstractionofresponsesandpermissionsinawebapplicationModularizingaprojecttoscaleOtherusefulasynchronoustechniques

Note

Again,duetothelimitedlength,someoftherelatedcodeisaggressivelysimplifiedandnothingmorethantheideaitselfcanbeappliedpractically.

Page 666: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Promise-basedwebarchitectureTohaveabetterunderstandingofthedifferencesbetweenPromisesandtraditionalcallbacks,consideranasynchronoustasklikethis:

functionprocess(callback){

stepOne((error,resultOne)=>{

if(error){

callback(error);

return;

}

stepTwo(resultOne,(error,resultTwo)=>{

if(error){

callback(error);

return;

}

callback(undefined,resultTwo+1);

});

});

}

IfwewriteprecedingaboveinPromisestyle,itwouldbeasfollows:

functionprocess(){

returnstepOne()

.then(result=>stepTwo(result))

.then(result=>result+1);

}

Asintheprecedingexample,Promisemakesiteasyandnaturaltowriteasynchronousoperationswithaflatchaininsteadofnestedcallbacks.ButthemostexcitingthingaboutPromisemightbethebenefitsitbringstoerrorhandling.InaPromise-basedarchitecture,throwinganerrorcanbesafeandpleasant.Youdon'thavetoexplicitlyhandleerrorswhenchainingasynchronousoperations,andthismakesmistakeslesslikelytohappen.

WiththegrowingusagewithES6compatibleruntimes,Promiseisalreadythereoutofthebox.AndweactuallyhaveplentyofpolyfillsforPromises(includingmyThenFailwritteninTypeScript),aspeoplewhowriteJavaScriptroughlyrefertothesamegroupofpeoplewhocreatedwheels.

PromisesworkwellwithotherPromises:

APromises/A+-compatibleimplementationshouldworkwithotherPromises/A+-compatibleimplementationsPromisesworkbestinaPromise-basedarchitecture

Page 667: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IfyouarenewtoPromise,youmightbecomplainingaboutusingPromiseswithacallback-basedproject.UsingasynchronoushelperssuchasPromise.each(non-standard)providedbyPromiselibrariesisacommonreasonforpeopletotryoutPromise,butitturnsouttheyhavebetteralternatives(foracallback-basedproject)suchasthepopularasynclibrary.

Thereasonthatmakesyoudecidetoswitchshouldnotbethesehelpers(astherearealotofthemforold-schoolcallbacksaswell),butaneasierwaytohandleerrorsortotakeadvantageoftheESasync/awaitfeature,whichisbasedonPromise.

Page 668: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PromisifyingexistingmodulesorlibrariesThoughPromisesdobestinaPromise-basedarchitecture,itisstillpossibletobeginusingPromisewithasmallerscopebypromisifyingexistingmodulesorlibraries.

Let'stakeNode.jsstylecallbacksasanexample:

import*asFSfrom'fs';

FS.readFile('some-file.txt','utf-8',(error,text)=>{

if(error){

console.error(error);

return;

}

console.log('Content:',text);

});

YoumayexpectapromisifiedversionofthereadFilefunctiontolooklikethefollowing:

FS

.readFile('some-file.txt','utf-8')

.then(text=>{

console.log('Content:',text);

})

.catch(reason=>{

Console.error(reason);

});

TheimplementationofthepromisifiedfunctionreadFilecanbeeasy:

functionreadFile(path:string,options:any):Promise<string>{

returnnewPromise((resolve,reject)=>{

FS.readFile(path,options,(error,result)=>{

if(error){

reject(error);

}else{

resolve(result);

}

});

});

}

Note

Iamusingthetypeanyhereforparameteroptionstoreducethesizeofthecodeexample,butIwouldsuggestnotusinganywheneverpossibleinpractice.

Therearelibrariesthatareabletopromisifymethodsautomatically.Though,unfortunately,youmightneedtowritedeclarationfilesyourselfforthepromisifiedmethodsiftherearenopromisifiedversionavailable.

Page 669: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ViewsandcontrollersinExpressManyofusmayhavealreadyworkedwithframeworkssuchasExpress.AndthisishowwerenderavieworresponsewithJSONinExpress:

import*asPathfrom'path';

import*asexpressfrom'express';

letapp=express();

app.set('engine','hbs');

app.set('views',Path.join(__dirname,'../views'));

app.get('/page',(req,res)=>{

res.render('page',{

title:'Hello,Express!',

content:'...'

});

});

app.get('/data',(req,res)=>{

res.json({

version:'0.0.0',

items:[]

});

});

app.listen(1337);

Wewillusuallyseparatecontrollersfromtheroutingconfiguration:

import{Request,Response}from'express';

exportfunctionpage(req:Request,res:Response):void{

res.render('page',{

title:'Hello,Express!',

content:'...'

});

}

Thuswemayhaveabetterideaofexistingroutes,andhavecontrollersmanagedmoreeasily.Furthermore,automatedroutingcouldbeintroducedsothatwedon'talwaysneedtoupdateroutingmanually:

import*asglobfrom'glob';

letcontrollersDir=Path.join(__dirname,'controllers');

letcontrollerPaths=glob.sync('**/*.js',{

cwd:controllersDir

});

for(letpathofcontrollerPaths){

letcontroller=require(Path.join(controllersDir,path));

Page 670: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

leturlPath=path.replace(/\\/g,'/').replace(/\.js$/,'');

for(letactionNameofObject.keys(controller)){

app.get(

`/${urlPath}/${actionName}`,

controller[actionName]

);

}

}

Theimplementationaboveiscertainlytoosimpletocoverdailyuse,butitshowsaroughideaofhowautomatedroutingcouldwork:viaconventionsbasedonfilestructures.

Now,ifweareworkingwithasynchronouscodewritteninPromises,anactioninthecontrollercouldbelikethefollowing:

exportfunctionfoo(req:Request,res:Response):void{

Promise

.all([

Post.getContent(),

Post.getComments()

])

.then(([post,comments])=>{

res.render('foo',{

post,

comments

});

});

}

Note

Wearedestructuringanarraywithinaparameter.Promise.allreturnsaPromiseofanarraywithelementscorrespondingtothevaluesoftheresolvablespassedin.(AresolvablemeansanormalvalueoraPromise-likeobjectthatmayresolvetoanormalvalue.)

Butthat'snotenough;westillneedtohandleerrorsproperly,orinsomePromiseimplementations,theprecedingcodemayfailinsilencebecausethePromisechainisnothandledbyarejectionhandler(whichisterrible).InExpress,whenanerroroccurs,youshouldcallnext(thethirdargumentpassedintothecallback)withtheerrorobject:

import{Request,Response,NextFunction}from'express';

exportfunctionfoo(

req:Request,

res:Response,

next:NextFunction

):void{

Promise

//...

.catch(reason=>next(reason));

}

Page 671: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Now,wearefinewiththecorrectnessofthisapproach,butthat'ssimplynothowPromiseswork.Expliciterrorhandlingwithcallbackscouldbeeliminatedinthescopeofcontrollers,andtheeasiestwayistoreturnthePromisechainandhandovertocodethatwaspreviouslydoingroutinglogic.Sothecontrollercouldbewrittenlikethis:

exportfunctionfoo(req:Request,res:Response){

returnPromise

.all([

Post.getContent(),

Post.getComments()

])

.then(([post,comments])=>{

res.render('foo',{

post,

comments

});

});

}

But,couldwemakeitevenbetter?

Page 672: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AbstractionofresponsesWe'vealreadybeenreturningaPromisetotellwhetheranerroroccurs.SonowthereturnedPromiseindicatesthestatusoftheresponse:successorfailure.Butwhywearestillcallingres.render()forrenderingtheview?Thereturnedpromiseobjectcouldbetheresponseitselfratherthanjustanerrorindicator.

Thinkaboutthecontrolleragain:

exportclassResponse{}

exportclassPageResponseextendsResponse{

constructor(view:string,data:any){}

}

exportfunctionfoo(req:Request){

returnPromise

.all([

Post.getContent(),

Post.getComments()

])

.then(([post,comments])=>{

returnnewPageResponse('foo',{

post,

comments

});

});

}

Theresponseobjectreturnedcouldvaryfordifferentresponseoutputs.Forexample,itcouldbeeitheraPageResponselikeitisintheprecedingexample,aJSONResponse,aStreamResponse,orevenasimpleRedirection.

As,inmostcases,PageResponseorJSONResponseisapplied,andtheviewofaPageResponsecanusuallybeimpliedbythecontrollerpathandactionname,itisusefultohavethosetworesponsesautomaticallygeneratedfromaplaindataobjectwithaproperviewtorenderwith:

exportfunctionfoo(req:Request){

returnPromise

.all([

Post.getContent(),

Post.getComments()

])

.then(([post,comments])=>{

return{

post,

comments

};

});

}

Page 673: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Andthat'showaPromise-basedcontrollershouldrespond.Withthisidea,let'supdatetheroutingcodewiththeabstractionofresponses.Previously,wewerepassingcontrolleractionsdirectlyasExpressrequesthandlers.Nowweneedtodosomewrappingupwiththeactionsbyresolvingthereturnvalue,andapplyingoperationsbasedontheresolvedresult:

1. Ifitfulfilsandit'saninstanceofResponse,applyittotheresobjectpassedinbyExpress.2. Ifitfulfilsandit'saplainobject,constructaPageResponseoraJSONResponseifnoview

foundandapplyittotheresobject.3. Ifitrejects,callthenextfunctionwiththereason.

Previously,itwaslikethis:

app.get(`/${urlPath}/${actionName}`,controller[actionName]);

Nowitgetsafewmorelines:

letaction=controller[actionName];

app.get(`/${urlPath}/${actionName}`,(req,res,next)=>{

Promise

.resolve(action(req))

.then(result=>{

if(resultinstanceofResponse){

result.applyTo(res);

}elseif(existsView(actionName)){

newPageResponse(actionName,result).applyTo(res);

}else{

newJSONResponse(result).applyTo(res);

}

})

.catch(reason=>next(reason));

});

However,sofarwecanhandleonlyGETrequestsaswehardcodedapp.get()inourrouterimplementation.Thepoorview-matchinglogiccanhardlybeusedinpracticeeither.Weneedtomaketheactionsconfigurable,andESdecoratorscoulddoniceworkhere:

exportdefaultclassController{

@get({

view:'custom-view-path'

})

foo(req:Request){

return{

title:'Actionfoo',

content:'Contentofactionfoo'

};

}

}

I'llleavetheimplementationtoyou,andfeelfreetomakeitawesome.

Page 674: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AbstractionofpermissionsPermissionsplayanimportantroleinaproject,especiallyinsystemsthathavedifferentusergroups,forexample,aforum.Theabstractionofpermissionsshouldbeextendabletosatisfychangingrequirements,anditshouldbeeasytouseaswell.

Here,wearegoingtotalkabouttheabstractionofpermissioninthelevelofcontrolleractions.Considerthelegibilityofperformingoneormoreactionsasaprivilege.Thepermissionofausermayconsistofseveralprivilegesandusuallymostusersatthesamelevelwouldhavethesamesetofprivileges.Sowemayhavealargerconcept,namelygroups.

Theabstractioncouldeitherworkbasedonbothgroupsandprivilegesorbasedonprivilegesonly(groupsarethenjustaliasestosetsofprivileges):

Abstractionsthatvalidatebasedonprivilegesandgroupsatthesametimeiseasiertobuild.Youdonotneedtocreatealargelistofwhichactionscanbeperformedforacertaingroupofusers;granularprivilegesareonlyrequiredwhennecessary.Abstractionsthatvalidatebasedonprivilegeshavebettercontrolandmoreflexibilityfordescribingthepermission.Forexample,youcanremoveasmallsetofprivilegesfromthepermissionofausereasily.

However,bothapproacheshavesimilarupper-levelabstractionsanddiffermostlyinimplementation.Thegeneralstructureofthepermissionabstractionswe'vetalkedaboutisasfollows:

Theparticipantsincludethefollowing:

Privilege:Describesdetailedprivilegescorrespondingtospecificactions

Page 675: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Group:DefinesasetofprivilegesPermission:Describeswhatauseriscapableofdoing;consistsofgroupstheuserbelongstoandprivilegestheuserhasPermissiondescriptor:Describeshowthepermissionofauserwouldbesufficient;consistsofpossiblegroupsandprivileges

Page 676: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExpectederrorsAgreatconcernwipedawaybyusingPromisesisthatwedonotneedtoworryaboutthrowinganerrorinacallbackwouldcrashtheapplicationmostofthetime.TheerrorwillflowthroughthePromiseschainand,ifnotcaught,willbehandledbyourrouter.Errorscanberoughlydividedintoexpectederrorsandunexpectederrors.Expectederrorsareusuallycausedbyincorrectinputorforeseeableexceptions,andunexpectederrorsareusuallycausedbybugsorotherlibrariestheprojectrelieson.

Forexpectederrors,weusuallywanttogiveuser-friendlyresponseswithreadableerrormessagesandcodes,sothatuserscanhelpthemselvestofindsolutionsorreporttouswithusefulcontext.Forunexpectederrors,wewouldalsowantreasonableresponses(usuallymessagesdescribedasunknownerrors),adetailedserver-sidelog(includingtherealerrorname,message,stackinformation,andsoon),andevenalarmsforgettingtheteamnotifiedassoonaspossible.

Definingandthrowingexpectederrors

Therouterwillneedtohandledifferenttypesoferrors,andaneasywaytoachievethatistosubclassauniversalExpectedErrorclassandthrowitsinstancesout:

importExtendableErrorfrom'extendable-error';

classExpectedErrorextendsExtendableError{

constructor(

message:string,

publiccode:number

){

super(message);

}

}

Note

Theextendable-errorisapackageofminethathandlesstacktraceandthemessageproperty.YoucandirectlyextendtheErrorclassaswell.

Thus,whenreceivinganexpectederror,wecansafelyoutputitsmessageaspartoftheresponse.Andifit'snotaninstanceofExpectedError,wecanthenoutputpredefinedunknownerrormessagesandhavedetailederrorinformationlogged.

Transformingerrors

Someerrors,suchasthosecausedbyunstablenetworksorremoteservices,areexpected;wemaywanttocatchthoseerrorsandthrowthemoutagainasexpectederrors.Butitisrathertrivialtoactuallydothat.Acentralizederror-transformingprocesscanthenbeappliedtoreducetheeffortsrequiredtomanagethoseerrors.

Page 677: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thetransformingprocessincludestwoparts:filtering(ormatching)andtransforming.Therearemanyapproachestofiltererrors,suchasthefollowing:

Filterbyerrorclass:Manythird-partylibrariesthrowerrorsofcertainclasses.TakingSequelize(apopularNode.jsORM)asanexample,itthrowsDatabaseError,ConnectionError,ValidationError,andsoon.Byfilteringerrorsbycheckingwhethertheyareinstancesofacertainerrorclass,wemayeasilypickuptargeterrorsfromthepile.Filterbystringorregularexpression:SometimesalibrarymightbethrowingerrorsthatareinstancesofanErrorclassitselfinsteadofitssubclasses;thismakesthoseerrorshardertodistinguishfromothers.Inthissituation,wemayfilterthoseerrorsbytheirmessage,withkeywordsorregularexpressions.Filterbyscope:It'spossiblethatinstancesofthesameerrorclasswiththesameerrormessageshouldresultindifferentresponses.Oneofthereasonsmightbethattheoperationthatthrowsacertainerrorisatalowerlevel,butisbeingusedbyupperstructureswithindifferentscopes.Thus,ascopemarkcouldbeaddedforthoseerrorsandmakethemeasiertobefiltered.

Therecouldbemorewaystofiltererrors,andtheyareusuallyabletocooperateaswell.Byproperlyapplyingthosefiltersandtransformingerrors,wecanreducenoiseforanalyzingwhat'sgoingonwithinasystemandlocateproblemsfasteriftheyshowup.

Page 678: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ModularizingprojectBeforeES6,therewerealotofmodulesolutionsforJavaScriptthatworked.ThetwomostfamousofthemareAMDandcommonjs.AMDisdesignedforasynchronousmoduleloading,whichismostlyappliedinbrowsers,whilecommonjsdoesmoduleloadingsynchronously,andthat'sthewaytheNode.jsmodulesystemworks.

Tomakeitworkasynchronously,writinganAMDmoduletakesmorecharacters.Andduetothepopularityoftoolssuchasbrowserifyandwebpack,commonjsbecomespopularevenforbrowserprojects.

Thepropergranularityofinternalmodulescouldhelpaprojectkeepitsstructurehealthy.Consideraprojectstructurelikethis:

project

├─controllers

├─core

││index.ts

││

│├─product

││index.ts

││order.ts

││shipping.ts

││

│└─user

│index.ts

│account.ts

│statistics.ts

├─helpers

├─models

├─utils

└─views

Assumewearewritingacontrollerfilethat'sgoingtoimportamoduledefinedbythecore/product/order.tsfile.Previously,withthecommonjsrequirestyle,wewouldwanttowritethefollowing:

constOrder=require('../core/product/order');

Now,withthenewESimportsyntax,itwouldbeasfollows:

import*asOrderfrom'../core/product/order';

Wait,isn'tthatessentiallythesame?Sortof.Butyoumayhavenoticedseveralindex.tsfilesI'veputintofolders.Now,inthefilecore/product/index.ts,wecanhavethefollowing:

import*asOrderfrom'./order';

import*asShippingfrom'./shipping';

export{Order,Shipping}

Page 679: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Alternatively,wecouldhavethefollowing:

export*from'./order';

export*from'./shipping';

What'sthedifference?Theideasbehindthosetwoapproachesofre-exportingmodulescanvary.ThefirststyleworksbetterwhenwetreatOrderandShippingasnamespaces,underwhichtheentitynamesmaynotbeeasytodistinguishfromonegrouptoanother.Withthisstyle,thefilesarethenaturalboundariesofbuildingthosenamespaces.Thesecondstyleweakensthenamespacepropertyoftwofilesandusesthemastoolstoorganizeobjectsandclassesunderthesamelargercategory.

Agoodthingaboutusingthosefilesasnamespacesisthatmultiple-levelre-exportingisfinewhileweakeningnamespacesmakesithardertounderstanddifferentidentifiernamesasthenumberofre-exportinglevelsgrows.

Page 680: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AsynchronouspatternsWhenwearewritingJavaScriptwithnetworkorfilesystemI/O,thereisa95%chancethatwearedoingitasynchronously.However,anasynchronouscodemaytremendouslydecreasethedeterminabilityatthedimensionoftime.ButwearesoluckythatJavaScriptisusuallysingle-threaded;thismakesitpossibleforustowritepredictablecodewithoutmechanismssuchaslocksmostofthetime.

Page 681: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WritingpredictablecodeThepredictablecodereliesonpredictabletools(ifyouareusingany).Considerahelperlikethis:

typeCallback=()=>void;

letisReady=false;

letcallbacks:Callback[]=[];

setTimeout(()=>{

callbacks.forEach(callback=>callback());

callbacks=undefined;

},100);

exportfunctionready(callback:Callback):void{

if(!callbacks){

callback();

}else{

callbacks.push(callback);

}

}

Thismoduleexportsareadyfunction,whichwillinvokethecallbackspassedinwhen"ready".Itwillassurethatcallbackswillbecalledevenifaddedafterthat.However,youcannotsayforsurewhetherthecallbackwillbecalledinthecurrenteventloop:

import{ready}from'./foo';

leti=0;

ready(()=>{

console.log(i);

});

i++;

Intheprecedingexample,icouldeitherbe0or1whenthecallbackgetscalled.Again,thisisnotwrong,orevenbad,itjustmakesthecodelesspredictable.Whensomeoneelsereadsthispieceofcode,heorshewillneedtoconsidertwopossibilitiesofhowthisprogramwouldrun.Toavoidthisissue,wecansimplywrapupthesynchronousinvocationwithsetImmediate(itmayfallbacktosetTimeoutinolderbrowsers):

exportfunctionready(callback:Callback):void{

if(!callbacks){

setImmediate(()=>callback());

}else{

callbacks.push(callback);

}

}

Writingpredictablecodeisactuallymorethanwritingpredictableasynchronouscode.ThehighlightedlineabovecanalsobewrittenassetImmediate(callback),butthatwouldmake

Page 682: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

peoplewhoreadyourcodethinktwice:howwillcallbackgetcalledandwhatarethearguments?

Considerthelineofcodebelow:

letresults=['1','2','3'].map(parseInt);

What'sthevalueofthearrayresults?Certainlynot[1,2,3].Becausethecallbackpassedtothemethodmapreceivesseveralarguments:valueofcurrentitem,indexofcurrentitem,andthewholearray,whilethefunctionparseIntacceptstwoarguments:stringtoparse,andradix.Soresultsareactuallytheresultsofthefollowingsnippet:

[parseInt('1',0),parseInt('2',1),parseInt('3',2)];

However,itisactuallyokaytowritesetImmediate(callback)directly,astheAPIsofthosefunctions(includingsetTimeout,setInterval,process.nextTick,andsoon)aredesignedtobeusedinthisway.Anditisfairtoassumepeoplewhoaregoingtomaintainthisprojectknowthataswell.Butforotherasynchronousfunctionswhosesignaturesarenotwellknown,itisrecommendedtocallthemwithexplicitarguments.

Page 683: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AsynchronouscreationalpatternsWetalkedaboutmanycreationalpatternsinChapter3,CreationalDesignPatterns.Whileaconstructorcannotbeasynchronous,someofthosepatternsmayhaveproblemsapplyingtoasynchronousscenarios.Butothersneedonlyslightmodificationsforasynchronoususe.

InChapter4,StructuralDesignPatternswewalkedthroughtheAdapterPatternwithastorageexamplethatopensthedatabaseandcreatesastorageobjectasynchronously:

classStorage{

privateconstructor(){}

open():Promise<Storage>{

returnopenDatabase()

.then(db=>newStorage(db))

}

}

AndintheProxyPattern,wemadethestorageobjectimmediatelyavailablefromitsconstructor.Whenamethodoftheobjectiscalled,itwaitsfortheinitializationtocompleteandfinishestheoperation:

classStorage{

privatedbPromise:Promise<IDBDatabase>;

getdbReady():Promise<IDBDatabase>{

if(this.dbPromise){

returnthis.dbPromise;

}

//...}

get<T>():Promise<T>{

returnthis

.dbReady

.then(db=>{

//...

});

}

}

Adrawbackofthisapproachisthatallmembersthatrelyoninitializationhavetobeasynchronous,thoughmostofthetimetheyjustareasynchronous.

Page 684: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AsynchronousmiddlewareandhooksTheconceptofmiddlewareiswidelyusedinframeworkssuchasExpress.Middlewareusuallyprocessesitstargetinserial.InExpress,middlewareisappliedroughlyintheorderitisaddedwhiletherearenotdifferentphases.Someotherframeworks,however,providehooksfordifferentphasesintime.Forexample,therearehooksthatwillbetriggeredbeforeinstall,afterinstall,afteruninstall,andsoon.

Note

ThemiddlewaremechanismofExpressisactuallyavariantoftheChainofResponsibilityPattern.Anddependingonthespecificmiddlewaretobeused,itcanactmoreorlesslikehooksinsteadofaresponsibilitychain.

Thereasonstoimplementmiddlewareorhooksvary.Theymayincludethefollowing:

Extensibility:Mostofthetime,theyareappliedduetotherequirementofextensibility.Newrulesandprocessescouldbeeasilyaddedbynewmiddlewareorhooks.Decouplinginteractionswithbusinesslogic:Amodulethatshouldonlycareaboutbusinesslogiccouldneedpotentialinteractionswithaninterface.Forexample,wemightexpecttobeabletoeitherenterorupdatecredentialswhileprocessinganoperation,withoutrestartingeverything.Thuswecancreateamiddlewareorahook,sothatwedon'tneedtohavethemtightlycoupled.

Theimplementationofasynchronousmiddlewarecouldbeinteresting.TakethePromiseversionasanexample:

typeMiddleware=(host:Host)=>Promise<void>;

classHost{

middlewares:Middleware[]=[];

start():Promise<void>{

returnthis

.middlewares

.reduce((promise,middleware)=>{

returnpromise.then(()=>middleware(this));

},Promise.resolve());

}

}

Here,we'reusingreducetodothetrick.WepassedinaPromisefulfilledwithundefinedastheinitialvalue,andchaineditwiththeresultofmiddleware(this).AndthisisactuallyhowthePromise.eachhelperisimplementedinmanyPromiselibraries.

Page 685: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Event-basedstreamparserWhencreatinganapplicationreliesonsocket,weusuallyneedalightweight"protocol"fortheclientandservertocommunicate.UnlikeXHRthatalreadyhandleseverything,byusingsocket,youwillneedtodefinetheboundariessodatawon'tbemixedup.

Datatransferredthroughasocketmightbeconcatenatedorsplit,butTCPconnectionensurestheorderandcorrectnessofbytesgetstransferred.Consideratinyprotocolthatconsistsofonlytwoparts:a4-byteunsignedintegerfollowedbyaJSONstringwithbytelengththatmatchesthe4-byteunsignedinteger.

Forexample,forJSON"{}",thedatapacketwouldbeasfollows:

Buffer<000000027b7d>

Tobuildsuchadatapacket,wejustneedtoconverttheJSONstringtoBuffer(withencodingsuchasutf-8,whichisdefaultencodingforNode.js),andthenprependitslength:

functionbuildPacket(data:any):Buffer{

letjson=JSON.stringify(data);

letjsonBuffer=newBuffer(json);

letpacket=newBuffer(4+jsonBuffer.length);

packet.writeUInt32BE(jsonBuffer.length,0);

jsonBuffer.copy(packet,4,0);

returnpacket;

}

Asocketclientemitsadataeventwhenitreceivesnewbuffers.AssumewearegoingtosendthefollowingJSONstrings:

//000000027b7d

{}

//0000000f7b226b6579223a2276616c7565227d

{"key":"value"}

Wemaybereceivingthemlikethis:

Gettwobuffersseparately;eachofthemisacompletepacketwithlengthandJSONbytesGetonesinglebufferwithtwobuffersconcatenatedGettwo,ormorethantwo,buffers;atleastoneofthepreviouslysentpacketsgetssplitintoseveralones.

Theentireprocessishappeningasynchronously.Butjustlikethesocketclientemitsadataevent,theparsercanjustemititsowndataeventwhenacompletepacketgetsparsed.Theparserforparsingourtinyprotocolmayhaveonlytwostates,correspondingtoheader

Page 686: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

(JSONbytelength)andbody(JSONbytes),andtheemittingofthedataeventhappensaftersuccessfullyparsingthebody:

classParserextendsEventEmitter{

privatebuffer=newBuffer(0);

privatestate=State.header;

append(buffer:Buffer):void{

this.buffer=Buffer.concat([this.buffer,buffer]);

this.parse();

}

privateparse():void{}

privateparseHeader():boolean{}

privateparseBody():boolean{}

}

Duetothelimitationoflength,I'mnotgoingtoputthecompleteimplementationoftheparserhere.Forthecompletecode,pleaserefertothefilesrc/event-based-parser.tsinthecodebundleofChapter7,PatternsandArchitecturesinJavaScriptandTypeScript.

Thustheuseofsuchaparsercouldbeasfollows:

import*asNetfrom'net';

letparser=newParser();

letclient=Net.connect(port);

client.on('data',(data:Buffer)=>{

parser.append(data);

});

parser.on('data',(data:any)=>{

console.log('Datareceived:',data);

});

Page 687: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wediscussedsomeinterestingideasandanarchitectureformedbythoseideas.Mostofthetopicsfocusonasmallscopeanddotheirownjob,buttherearealsoideasaboutputtingawholesystemtogether.

Thecodethatimplementstechniquessuchasexpectederrorandtheapproachtomanagingmodulesinaprojectisnothardtoapply.Butwithproperapplication,itcanbringnotableconveniencetotheentireproject.

However,asIhavealreadymentionedatthebeginningofthischapter,therearetoomanybeautifulthingsinJavaScriptandTypeScripttobecoveredorevenmentionedinasinglechapter.Pleasedon'tstophere,andkeepexploring.

Manypatternsandarchitecturesaretheresultofsomefundamentalprinciplesinsoftwareengineering.Thoseprinciplesmightnotalwaysbeapplicableineveryscenario,buttheymayhelpwhenyoufeelconfused.Inthenextchapter,wearegoingtotalkaboutSOLIDprinciplesinobject-orienteddesignandfindouthowthoseprinciplesmayhelpformausefulpattern.

Page 688: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter8.SOLIDPrinciplesSOLIDPrinciplesarewell-knownObject-OrientedDesign(OOD)principlessummarizedbyUncleBob(RobertC.Martin).ThewordSOLIDcomesfromtheinitialsofthefiveprinciplesitrefersto,includingSingleresponsibilityprinciple,Open-closedprinciple,Liskovsubstitutionprinciple,InterfacesegregationprincipleandDependencyinversionprinciple.Thoseprinciplesarecloselyrelatedtoeachother,andcanbeagreatguidanceinpractice.

HereisawidelyusedsummaryofSOLIDprinciplesfromUncleBob:

Singleresponsibilityprinciple:Aclassshouldhaveone,andonlyone,reasontochangeOpen-closedprinciple:Youshouldbeabletoextendaclassesbehavior,withoutmodifyingitLiskovsubstitutionprinciple:DerivedclassesmustbesubstitutablefortheirbaseclassesInterfacesegregationprinciple:Makefine-grainedinterfacesthatareclientspecificDependencyinversionprinciple:Dependonabstractions,notonconcretions

Inthischapter,wewillwalkthroughthemandfindouthowthoseprinciplescanhelpformadesignthatsmellsnice.

Butbeforeweproceed,Iwanttomentionthatafewofthereasonswhythoseprinciplesexistmightberelatedtotheageinwhichtheywereraised,thelanguagesandtheirbuildingordistributingprocesspeoplewereworkingwith,andevencomputingresources.WhenbeingappliedtoJavaScriptandTypeScriptprojectsnowadays,someofthedetailsmaynotbenecessary.Thinkmoreaboutwhatproblemsthoseprincipleswanttopreventpeoplefromgettinginto,ratherthantheliteraldescriptionsofhowaprincipleshouldbefollowed.

Page 689: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SingleresponsibilityprincipleThesingleresponsibilityprincipledeclaresthataclassshouldhaveone,andonlyonereasontochange.Andthedefinitionoftheworldreasoninthissentenceisimportant.

Page 690: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExampleConsideraCommandclassthatisdesignedtoworkwithbothcommand-lineinterfaceandgraphicaluserinterface:

classCommand{

environment:Environment;

print(items:ListItem[]){

letstdout=this.environment.stdout;

stdout.write('Items:\n');

for(letitemofitems){

stdout.write(item.text+'\n');

}

}

render(items:ListItem[]){

letelement=<Listitems={items}></List>;

this.environment.render(element);

}

execute(){}

}

Tomakethisactuallywork,executemethodwouldneedtohandleboththecommandexecutionandresultdisplaying:

classCommand{

..

execute(){

letitems=...;

if(this.environment.type==='cli'){

this.print(items);

}else{

this.render(items);

}

}

}

Inthisexample,therearetworeasonsforchanges:

1. Howacommandgetsexecuted.2. Howtheresultofacommandgetsdisplayedindifferentenvironments.

Page 691: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thosereasonsleadtochangesindifferentdimensionsandviolatethesingleresponsibilityprinciple.Thismightresultinamessysituationovertime.AbettersolutionistohavethosetworesponsibilitiesseparatedandmanagedbytheCommandEnvironment:

Doesthislookfamiliartoyou?BecauseitisavariantoftheVisitorPattern.Nowitistheenvironmentthatexecutesaspecificcommandandhandlesitsresultbasedonaconcreteenvironmentclass.

Page 692: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChoosinganaxisYoumightbethinking,doesn'tCommandResultviolatethesingleresponsibilityprinciplebyhavingtheabilitiestodisplaycontentinadifferentenvironment?Yes,andno.Whentheaxisofthisreasonissettodisplayingcontent,itdoesnot;butiftheaxisissettodisplayinginaspecificenvironment,itdoes.Buttaketheoverallstructureintoconsideration,theresultofacommandisexpectedtobeanoutputthatcanadapttoadifferentenvironment.Andthusthereasonisone-dimensionalandconfirmstheprinciple.

Page 693: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Open-closedprincipleTheopen-closedprincipledeclaresthatyoushouldbeabletoextendaclass'behavior,withoutmodifyingit.ThisprincipleisraisedbyBertrandMeyerin1988:

Softwareentities(classes,modules,functions,etc.)shouldbeopenforextension,butclosedformodification.

Aprogramdependsonalltheentitiesituses,thatmeanschangingthealready-being-usedpartofthoseentitiesmayjustcrashtheentireprogram.Sotheideaoftheopen-closedprincipleisstraightforward:we'dbetterhaveentitiesthatneverchangeinanywayotherthanextendingitself.

Thatmeansonceatestiswrittenandpassing,ideally,itshouldneverbechangedfornewlyaddedfeatures(anditneedstokeeppassing,ofcourse).Again,ideally.

Page 694: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExampleConsideranAPIhubthathandlesHTTPrequeststoandresponsesfromtheserver.Wearegoingtohaveseveralfileswrittenasmodules,includinghttp-client.ts,hub.tsandapp.ts(butwewon'tactuallywritehttp-client.tsinthisexample,youwillneedtousesomeimagination).

Savethecodebelowasfilehub.ts.

import{HttpClient,HttpResponse}from'./http-client';

exportfunctionupdate():Promise<HttpResponse>{

letclient=newHttpClient();

returnclient.get('/api/update');

}

Andsavethecodebelowasfileapp.ts.

importHubfrom'./hub';

Hub

.update()

.then(response=>JSON.stringify(response.text))

.then(result=>{

console.log(result);

});

Bravelydone!Nowwehaveapp.tsbadlycoupledwithhttp-client.ts.AndifwewanttoadaptthisAPIhubtosomethinglikeWebSocket,BANG.

Sohowcanwecreateentitiesthatareopenforextension,butclosedformodification?Thekeyisastableabstractionthatadapts.ConsiderthestorageandclientexamplewetookwithAdapterPatterninChapter4,StructuralDesignPatternswehadaStorageinterfacethatisolatesimplementationofdatabaseoperationsfromtheclient.Andassumingthattheinterfaceiswell-designedtomeetupcomingfeaturerequirements,itispossiblethatitwillneverchangeorjustneedtobeextendedduringthelifecycleoftheprogram.

Page 695: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AbstractioninJavaScriptandTypeScriptGuesswhat,ourbelovedJavaScriptdoesnothaveaninterface,anditisdynamicallytyped.Wewerenotevenabletoactuallywriteaninterface.However,wecouldstillwritedowndocumentationabouttheabstractionandcreatenewconcreteimplementationsjustbyobeyingthatdescription.

ButTypeScriptoffersinterface,andwecancertainlytakeadvantageofit.ConsidertheCommandResultclassintheprevioussection.Wewerewritingitasaconcreteclass,butitmayhavesubclassesthatoverridetheprintorrendermethodforcustomizedoutput.However,thetypesysteminTypeScriptcaresonlyabouttheshapeofatype.Thatmeans,whileyouaredeclaringanentitywithtypeCommandResult,theentitydoesnotneedtobeaninstanceofCommandResult:anyobjectwithacompatibletype(namelyhasmethodsprintandrenderwithpropersignaturesinthiscase)willdothejob.

Forexample,thefollowingcodeisvalid:

letenvironment:Environment;

letcommand:Command={

environment,

print(items){},

render(items){},

execute(){}

};

Page 696: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RefactorearlierIdoublestressedthattheopen-closedprinciplecanonlybeperfectlyfollowedunderidealscenarios.Thatcanbearesultoftworeasons:

1. Notallentitiesinasystemcanbeopentoextensionandclosedtomodificationatthesametime.Therewillalwaysbechangesthatneedtobreaktheclosureofexistingentitiestocompletetheirfunctionalities.Whenwearedesigningtheinterfaces,weneeddifferentstrategiesforcreatingstableclosuresfordifferentforeseeablesituations.Butthisrequiresnotableexperienceandnoonecandoitperfectly.

2. Noneofusistoogoodatdesigningaprogramthatlastslongandstayshealthyforever.Evenwiththoroughconsideration,abstractionsdesignedatthebeginningcanbechoppyfacingthechangingrequirements.

Sowhenweareexpectingtheentitiestobeclosedformodification,itdoesnotmeanthatweshouldjuststandthereandwatchitbeingclosed.Instead,whenthingsarestillundercontrol,weshouldrefactorandkeeptheabstractioninthestatusofbeingopentoextensionandclosedtomodificationatthetimepointofrefactoring.

Page 697: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

LiskovsubstitutionprincipleTheopen-closedprincipleistheessentialprincipleofkeepingcodemaintainableandreusable.Andthekeytotheopen-closedprincipleisabstractionwithpolymorphism.Behaviorslikeimplementinginterfaces,orextendingclassesmakepolymorphicshapes,butthatmightnotbeenough.

TheLiskovsubstitutionprincipledeclaresthatderivedclassesmustbesubstitutablefortheirbaseclasses.OrinthewordsofBarbaraLiskov,whoraisedthisprinciple:

Whatiswantedhereissomethinglikethefollowingsubstitutionproperty:Ifforeachobjecto1oftypeSthereisanobjecto2oftypeTsuchthatforallprogramsPdefinedintermsofT,thebehaviorofPisunchangedwheno1issubstitutedforo2thenSisasubtypeofT.

Nevermind.Let'stryanotherone:anyforeseeableusageoftheinstanceofaclassshouldbeworkingwiththeinstancesofitsderivedclasses.

Page 698: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExampleAndherewegowithastraightforwardviolationexample.ConsiderNoodlesandInstantNoodles(asubclassofNoodles)tobecooked:

functioncookNoodles(noodles:Noodles){

if(noodlesinstanceofInstantNoodles){

cookWithBoiledWaterAndBowl(noodles);

}else{

cookWithWaterAndBoiler(noodles);

}

}

Nowifwewanttohavesomefriednoodles...ThecookNoodlesfunctiondoesnotseemtobecapableofhandlingthat.Clearly,thisviolatestheLiskovsubstitutionprinciple,thoughitdoesnotmeanthatit'sabaddesign.

Let'sconsideranotherexamplewrittenbyUncleBobinhisarticletalkingaboutthisprinciple.WearecreatingclassSquarewhichisasubclassofRectangle,butinsteadofaddingnewfeatures,itaddsaconstrainttoRectangle:thewidthandheightofasquareshouldalwaysbeequaltoeachother.AssumewehaveaRectangleclassthatallowsitswidthandheighttobeset:

classRectangle{

constructor(

private_width:number;

private_height:number;

){}

setwidth(value:number){

this._width=value;

}

setheight(value:number){

this._height=value;

}

}

NowwehaveaproblemwithitssubclassSquare,becauseitgetswidthandheightsettersfromRectanglewhileitshouldn't.Wecancertainlyoverridethosesettersandmakebothofthemupdatewidthandheightsimultaneously.Butinsomesituations,theclientmightjustnotwantthat,becausedoingsowillmaketheprogramhardertobepredicted.

TheSquareandRectangleexampleviolatestheLiskovsubstitutionprinciple.Notbecausewedidn'tfindagoodwaytoinherit,butbecauseSquaredoesnotconformthebehaviorofRectangleandshouldnotbeasubclassofitatthebeginning.

Page 699: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheconstraintsofsubstitutionTypeisanimportantpartinaprogramminglanguage,eveninJavaScript.Buthavingthesameshape,beingonthesamehierarchydoesnotmeantheycanbethesubstitutionofanotherwithoutsomepain.Morethanjusttheshape,thecompletebehavioriswhatreallymattersforimplementationsthatholdtotheLiskovsubstitutionprinciple.

Page 700: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InterfacesegregationprincipleWe'vealreadydiscussedtheimportantroleplayedbyabstractionsinobject-orienteddesign.Theabstractionsandtheirderivedclasseswithoutseparationusuallycomeupwithhierarchicaltreestructures.Thatmeanswhenyouchoosetocreateabranch,youcreateaparallelabstractiontoallofthoseonanotherbranch.

Forafamilyofclasseswithonlyonelevelofinheritance,thisisnotaproblem:becauseitisjustwhatyouwanttohavethoseclassesderivedfrom.Butforahierarchywithgreaterdepth,itcouldbe.

Page 701: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExampleConsidertheTextReaderexamplewetookwithTemplateMethodPatterninChapter6,BehavioralDesignPatterns:ContinuouswehadFileAsciiTextReaderandHttpAsciiTextReaderderivedfromAsciiTextReader.ButwhatifwewanttohaveotherreadersthatunderstandUTF-8encoding?

Toachievethatgoal,wehavetwocommonoptions:separatetheinterfaceintotwofordifferentobjectsthatcooperate,orseparatetheinterfaceintotwothengetthemimplementedbyasingleclass.

Forthefirstcase,wecanrefactorthecodewithtwoabstractions,BytesReaderandTextReader:

Andforthesecondcase,wecanseparatemethodreadAllBytesanddecodeBytesontotwointerfaces,forexample,BytesReaderandBytesDecoder.Thuswemayimplementthemseparatelyandusetechniqueslikemixintoputthemtogether:

Page 702: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AninterestingpointaboutthisexampleisthatTextReaderaboveitselfisanabstractclass.Tomakethismixinactuallywork,weneedtocreateaconcreteclassofTextReader(withoutactuallyimplementingreadAllBytesanddecodeBytes),andthenmixintwoconcreteclassesofBytesReaderandBytesDecoder.

Page 703: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PropergranularityItissaidthatbycreatingsmallerinterfaces,wecanavoidaclientfromusingbigclasseswithfeaturesthatitneverneeds.Thismaycauseunnecessaryusageofresources,butinpractice,thatusuallywon'tbeaproblem.Themostimportantpartoftheinterfacesegregationprincipleisstillaboutkeepingcodemaintainableandreusable.

Thenthequestioncomesoutagain,howsmallshouldaninterfacebe?Idon'tthinkIhaveasimpleanswerforthat.ButIamsurethatbeingtoosmallmightnothelp.

Page 704: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DependencyinversionprincipleWhenwetalkaboutdependencies,thenaturalsenseisaboutdependenciesfrombottomtotop,justlikehowbuildingsarebuilt.Butunlikeabuildingthatstandsfortensofyearswithlittlechange,softwarekeepschangingduringitslifecycle.Everychangecosts,moreorless.

Thedependencyinversionprincipledeclaresthatentitiesshoulddependonabstractions,notonconcretions.Higherlevelcodeshouldnotdependdirectlyonlow-levelimplementations,instead,itshoulddependonabstractionsthatleadtothoseimplementations.Andthisiswhythingsareinverse.

Page 705: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ExampleStilltakingtheHTTPclientandAPIhubasanexample,whichobviouslyviolatesthedependencyinversionprinciple,takingtheforeseeableapplicationintoconsideration,whattheAPIhubshoulddependonisamessagingmechanismbridgingclientandserver,butnotbareHTTPclient.ThismeansweshouldhaveanabstractionlayerofmessagingbeforetheconcreteimplementationofHTTPclient:

Page 706: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SeparatinglayersComparedtootherprinciplesdiscussedinthischapter,thedependencyinversionprinciplecaresmoreaboutthescopeofmodulesorpackages.Astheabstractionmightusuallybemorestablethanconcreteimplementations,byfollowingdependencyinversionprinciple,wecanminimizetheimpactfromlow-levelchangestohigherlevelbehaviors.

ButforJavaScript(orTypeScript)projectsasthelanguageisdynamicallytyped,thisprincipleismoreaboutanideaofguidancethatleadstoastableabstractionbetweendifferentlayersofcodeimplementation.

Originally,animportantbenefitoffollowingthisprincipleisthat,ifmodulesorpackagesarerelativelylarger,separatingthembyabstractioncouldsavealotoftimeincompilation.ButforJavaScript,wedon'thavetoworryaboutthat;andforTypeScript,wedon'thavetorecompiletheentireprojectformakingchangestoseparatedmoduleseither.

Page 707: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wewalkedthroughthewell-knownSOLIDprincipleswithsimpleexamples.Sometimes,followingthoseprinciplescouldleadustoausefuldesignpattern.Andwealsofoundthatthoseprinciplesarestronglyboundtoeachother.Usuallyviolatingoneofthemmayindicateotherviolations.

ThoseprinciplescouldbeextremelyhelpfulforOOD,butcouldalsobeoverkilliftheyareappliedwithoutproperadaptions.Awell-designedsystemshouldhavethoseprinciplesconfirmedjustright,oritmightharm.

Inthenextchapter,insteadoftheories,we'llhavemoretimewithacompleteworkflowwithtestingandcontinuousintegrationinvolved.

Page 708: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter9.TheRoadtoEnterpriseApplicationAfterwalkingthroughcommondesignpatterns,wehavenowthebasisofcodedesigning.However,softwareengineeringismoreaboutwritingbeautifulcode.Whilewearetryingtokeepthecodehealthyandrobust,westillhavealottodotokeeptheprojectandtheteamhealthy,robust,andreadytoscale.Inthischapter,we'lltalkaboutpopularelementsintheworkflowofwebapplications,andhowtodesignaworkflowthatfitsyourteam.

Thefirstpartwouldbesettingupthebuildstepsofourdemoproject.We'llquicklywalkthroughhowtobuildfrontendprojectswithwebpack,oneofthemostpopularpackagingtoolsthesedays.Andwe'llconfiguretests,codelinter,andthensetupcontinuousintegration.

Thereareplentyofnicechoiceswhenitcomestoworkflowintegration.Personally,IpreferTeamFoundationServerforprivateprojectsoracombinationofGitHubandTravis-CIforopen-sourceprojects.WhileTeamFoundationServer(orVisualStudioTeamServicesasitscloud-basedversion)providesaone-stopsolutionfortheentireapplicationlifecycle,thecombinationofGitHubandTravis-CIismorepopularintheJavaScriptcommunity.Inthischapter,wearegoingusetheservicesprovidedbyGitHubandTravis-CIforourworkflow.

Herearewhatwearegoingtowalkthrough:

Packagingfrontendassetswithwebpack.Settinguptestsandlinter.GettingourhandsonaGitflowbranchingmodelandotherGit-relatedworkflow.ConnectingaGitHubrepositorywithTravis-CI.Apeekintoautomateddeployment.

Page 709: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatinganapplicationWe'vetalkedaboutcreatingTypeScriptapplicationsforbothfrontendandbackendprojectsintheChapter1,ToolsandFrameworks.AndnowwearegoingtocreateanapplicationthatcontainstwoTypeScriptprojectsatthesametime.

Page 710: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DecisionbetweenSPAand"normal"webapplicationsApplicationsfordifferentpurposesresultindifferentchoices.SPA(singlepageapplication)usuallydeliversabetteruserexperienceafterbeingloaded,butitcanalsoleadtotrade-offsonSEOandmayrelyonmorecomplexMV*frameworkslikeAngular.

OnesolutiontobuildSEO-friendlySPAistobuildauniversal(orisomorphic)applicationthatrunsthesamecodeonbothfrontendandbackend,butthatcouldintroduceevenmorecomplexity.OrareverseproxycouldbeconfiguredtorenderautomaticallygeneratedpageswiththehelpoftoolslikePhantom.

Inthisdemoproject,we'llchooseamoretraditionalwebapplicationwithmultiplepagestobuild.Andhere'sthefilestructureoftheclientproject:

Page 711: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TakingteamcollaborationintoconsiderationBeforeweactuallystartcreatingareal-worldapplication,weneedtocomeupwithareasonableapplicationstructure.Aproperapplicationstructureismorethansomethingunderwhichthecodecompilesandruns.Itshouldbearesult,takinghowyourteammembersworktogetherintoconsideration.

Forexample,anamingconventionisinvolvedinthisdemoclientstructureshownearlier:pageassetsarenamedafterpagenamesinsteadoftheirtypes(forexample,style.scss)ornameslikeindex.ts.Andtheconsiderationbehindthisconventionismakingitmorefriendlyforfilenavigationbythekeyboard.

Ofcourse,thisconsiderationisvalidonlyifasignificantnumberofdevelopersinyourteamarecoolwithkeyboardnavigation.Otherthanoperationpreferences,theexperiencesandbackgroundsofateamshouldbeseriouslyconsideredaswell:

Shouldthe"full-stack"modebeenabledforyourteam?Shouldthe"full-stack"modebeenabledforeveryengineerinyourteam?Howshouldyoudivideworkbetweenfrontendandbackend?

Usually,it'snotnecessaryandnotefficienttolimittheaccessofafrontendengineertoclient-sidedevelopment.Ifit'spossible,frontendengineerscouldtakeoverthecontrollerlayerofthebackendandleavehardcorebusinessmodelsandlogictoengineersthatfocusmoreonthebackend.

Wearehavingtheclientandserver-sideprojectsinthesamerepositoryforaneasierintegrationduringdevelopment.Butitdoesnotmeaneverythinginthefrontendorbackendcodebaseshouldbeinthissinglerepository.Instead,multiplemodulescouldbeextractedandmaintainedbydifferentdevelopersinpractice.Forexample,youcanhavedatabasemodelsandbusinesslogicmodelsseparatedfromthecontrollersonthebackend.

Page 712: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BuildingandtestingprojectsWehavealreadytalkedaboutbuildingandtestingTypeScriptprojectsatthebeginningofthisbook.Inthissection,wewillgoalittlebitfurtherforfrontendprojects,includingthebasisofusingWebpacktoloadstaticassetsaswellascodelinting.

Page 713: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StaticassetspackagingwithwebpackModularizinghelpscodekeepahealthystructureandmakesitmaintainable.However,itcouldleadtoperformanceissuesifdevelopment-timecodewritteninsmallmodulesaredirectlydeployedwithoutbundlingforproductionusage.Sostaticassetspackagingbecomesaserioustopicoffrontendengineering.

Backtotheolddays,packagingJavaScriptfileswasjustaboutuglifyingsourcecodeandconcatenatingfilestogether.Theprojectmightbemodularizedaswell,butinaglobalway.ThenwehavelibrarieslikeRequire.js,withmodulesnolongerautomaticallyexposingthemselvestotheglobalscope.

ButasIhavementioned,havingtheclientdownloadmodulefilesseparatelyisnotidealforperformance;soonwehadtoolslikebrowserify,andlater,webpack-oneofthemostpopularfrontendpackagingtoolsthesedays.

Introductiontowebpack

Webpackisanintegratedpackagingtooldedicated(atleastatthebeginning)tofrontendprojects.ItisdesignedtopackagenotonlyJavaScript,butalsootherstaticassetsinafrontendproject.Webpackprovidesbuilt-insupportforbothasynchronousmoduledefinition(AMD)andcommonjs,andcanloadES6orothertypesofresourcesviaplugins.

Note

ES6modulesupportwillgetbuilt-inforwebpack2.0,butbythetimethischapteriswritten,youstillneedpluginslikebabel-loaderorts-loadertomakeitwork.Andofcoursewearegoingtousets-loaderlater.

Toinstallwebpackvianpm,executethefollowingcommand:

$npminstallwebpack-g

BundlingJavaScript

BeforeweactuallyusewebpacktoloadTypeScriptfiles,we'llhaveaquickwalkthroughofbundlingJavaScript.

First,let'screatethefileindex.jsunderthedirectoryclient/src/withthefollowingcodeinside:

varFoo=require('./foo');

Foo.test();

Thencreatethefilefoo.jsinthesamefolderwiththefollowingcontent:

exports.test=functiontest(){

Page 714: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

console.log('Hello,Webpack!');

};

Nowwecanhavethembundledasasinglefileusingthewebpackcommand-lineinterface:

$webpack./client/src/index.js./client/out/bundle.js

Byviewingthebundle.jsfilegeneratedbywebpack,youwillseethatthecontentsofbothindex.jsandfoo.jshavebeenwrappedintothatsinglefile,togetherwiththebootstrapcodeofwebpack.Ofcourse,wewouldprefernottotypethosefilepathsinthecommandlineeverytime,buttouseaconfigurationfileinstead.

WebpackprovidesconfigurationfilesupportintheformofJavaScriptfiles,whichmakesitmoreflexibletogeneratenecessarydatalikebundleentriesautomatically.Let'screateasimpleconfigurationfilethatdoeswhatthepreviouscommanddid.

Createfileclient/webpack.config.jswiththefollowinglines:

'usestrict';

constPath=require('path');

module.exports={

entry:'./src/index',

output:{

path:Path.join(__dirname,'out'),

filename:'bundle.js'

}

};

Thesearethetwothingstomention:

1. Thevalueoftheentryfieldisnotthefilename,butthemoduleid(mostofthetimethisisunresolved)instead.Thismeansthatyoucanhavethe.jsextensionomitted,buthavetoprefixitwith./or../bydefaultwhenreferencingafile.

2. Theoutputpathisrequiredtobeabsolute.Buildinganabsolutepathwith__dirnameensuresitworksproperlyifwearenotexecutingwebpackunderthesamedirectoryastheconfigurationfile.

LoadingTypeScript

NowwearegoingtoloadandtranspileourbelovedTypeScriptusingthewebpackplugints-loader.Beforeupdatingtheconfiguration,let'sinstallthenecessarynpmpackages:

$npminstalltypescriptts-loader--save-dev

Ifthingsgowell,youshouldhavetheTypeScriptcompileraswellasthets-loaderplugininstalledlocally.Wemayalsowanttorenameandupdatethefilesindex.jsandfoo.jstoTypeScriptfiles.

Page 715: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Renameindex.jstoindex.tsandupdatethemoduleimportingsyntax:

import*asFoofrom'./foo';

Foo.test();

Renamefoo.jstofoo.tsandupdatethemoduleexportingsyntax:

exportfunctiontest(){

console.log('Hello,Webpack!');

}

Ofcourse,wewouldwanttoaddthetsconfig.jsonfileforthoseTypeScriptfiles(inthefolderclient):

{

"compilerOptions":{

"target":"es5",

"module":"commonjs"

},

"exclude":[

"out",

"node_modules"

]

}

Note

ThecompileroptionoutDirisomittedherebecauseitismanagedinthewebpackconfigurationfile.

TomakewebpackworkwithTypeScriptviats-loader,we'llneedtotellwebpacksomeinformationintheconfigurationfile:

1. Webpackwillneedtoresolvefileswith.tsextensions.Webpackhasadefaultextensionslisttoresolve,including''(emptystring),'.webpack.js','.web.js',and'.js'.Weneedtoadd'.ts'tothislistforittorecognizeTypeScriptfiles.

2. Webpackwillneedtohavets-loaderloading.tsmodulesbecauseitdoesnotcompileTypeScriptitself.

Andhereistheupdatedwebpack.config.js:

'usestrict';

constPath=require('path');

module.exports={

entry:'./src/index',

output:{

path:Path.join(__dirname,'bld'),

filename:'bundle.js'

},

Page 716: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

resolve:{

extensions:['','.webpack.js','.web.js','.ts','.js']

},

module:{

loaders:[

{test:/\.ts$/,loader:'ts-loader'}

]

}

};

Nowexecutethecommandwebpackundertheclientfolderagain,weshouldgetthecompiledandbundledoutputasexpected.

Duringdevelopment,wecanenabletranspilemode(correspondingtothecompileroptionisolatedModules)ofTypeScripttohavebetterperformanceoncompilingchangingfiles.Butitmeanswe'llneedtorelyonanIDEoraneditortoprovideerrorhints.Andremembertomakeanothercompilationwithtranspilemodedisabledafterdebuggingtoensurethingsstillwork.

Toenabletranspilemode,addatsfield(definedbythets-loaderplugin)withtranspileOnlysettotrue:

module.exports={

...

ts:{

transpileOnly:true

}

};

Splittingcode

Totaketheadvantageofcodecachingacrosspages,wemightwanttosplitthepackagedmodulesascommonpieces.Thewebpackprovidesabuilt-inplugincalledCommonsChunkPluginthatcanpickoutcommonmodulesandhavethempackedseparately.

Forexample,ifwecreateanotherfilecalledbar.tsthatimportsfoo.tsjustlikeindex.tsdoes,foo.tscanbetreatedasacommonchunkandbepackedseparately:

module.exports={

entry:['./src/index','./src/bar'],

...

plugins:[

newWebpack.optimize.CommonsChunkPlugin({

name:'common',

filename:'common.js'

})

]

};

Formulti-pageapplications,itiscommontohavedifferentpageswithdifferententryscripts.Insteadofmanuallyupdatingtheentryfieldintheconfigurationfile,wecantakeadvantage

Page 717: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ofitbeingJavaScriptandgenerateproperentriesautomatically.Todoso,wemightwantthehelpofthenpmpackageglobformatchingpageentries:

$npminstallglob--saved-dev

Andthenupdatethewebpackconfigurationfile:

constglob=require('glob');

module.exports={

entry:glob

.sync('./src/pages/*/*.ts')

.filter(path=>

Path.basename(path,'.ts')===

Path.basename(Path.dirname(path))

),

...

};

Splittingthecodecanberatheracomplextopicfordeepdive,sowe'llstophereandletyouexplore.

Loadingotherstaticassets

Aswe'vementioned,webpackcanalsobeusedtoloadotherstaticassetslikestylesheetanditsextensions.Forexample,youcanusethecombinationofstyle-loader,css-loaderandsass-loader/less-loadertoload.sass/.lessfiles.

Theconfigurationissimilartots-loadersowe'llnotspendextrapagesfortheirintroductions.Formoreinformation,refertothefollowingURLs:

Embeddedstylesheetsinwebpack:https://webpack.github.io/docs/stylesheets.htmlSASSloaderforwebpack:https://github.com/jtangelder/sass-loaderLESSloaderforwebpack:https://github.com/webpack/less-loader

Page 718: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingTSLinttoprojectsAconsistentcodestyleisanimportantfactorofcodequality,andlintersareourbestfriendswhenitcomestocodestyles(andtheyalsohelpswithcommonmistakes).ForTypeScriptlinting,TSLintiscurrentlythesimplestchoice.

TheinstallationandconfigurationofTSLintareeasy.Tobeginwith,let'sinstalltslintasaglobalcommand:

$npminstalltslint-g

Andthenweneedtoinitializeaconfigurationfileusingthefollowingcommandundertheprojectrootdirectory:

$tslint--init

TSLintwillthengenerateadefaultconfigurationfilenamedtslint.json,andyoumaycustomizeitbasedonyourownpreferences.AndnowwecanuseittolintourTypeScriptsourcecode:

$tslint*/src/**/*.ts

Page 719: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IntegratingwebpackandtslintcommandwithnpmscriptsAswe'vementionedbefore,anadvantageofusingnpmscriptsisthattheycanhandlelocalpackageswithexecutablesproperlybyaddingnode_modules/.bintoPATH.Andtomakeourapplicationeasiertobuildandtestforotherdevelopers,wecanhavewebpackandtslintinstalledasdevelopmentdependenciesandaddrelatedscriptstopackage.json:

"scripts":{

"build-client":"cdclient&&webpack",

"build-server":"tsc--projectserver",

"build":"npmrunbuild-client&&npmrunbuild-server",

"lint":"tslint./*/src/**/*.ts",

"test-client":"cdclient&&mocha",

"test-server":"cdserver&&mocha",

"test":"npmrunlint&&npmruntest-client&&npmruntest-server"

}

Page 720: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

VersioncontrolThinkingbacktomyseniorhighschooldays,Iknewnothingaboutversioncontroltools.ThebestthingIcoulddowastocreateadailyarchiveofmycodeonaUSBdisk.AndyesIdidloseone!

Nowadays,withtheboomofversioncontroltoolslikeGitandtheavailabilitiesofmultiplefreeserviceslikeGitHubandVisualStudioTeamServices,managingcodewithversioncontroltoolshasbecomeadailybasisforeverydeveloper.

Asthemostpopularversioncontroltool,Githasalreadybeenplayinganimportantroleinyourworkorpersonalprojects.Inthissection,we'lltalkaboutpopularpracticesofusingGitinateam.

Note

NotethatIamassumingthatyoualreadyhavethebasicknowledgeofGit,andknowhowtomakeoperationslikeinit,commit,push,pullandmerge.Ifnot,pleasegethandsonandtrytounderstandthoseoperationsbeforecontinue.

Note

Checkoutthisquicktutorialat:https://try.github.io/.

Page 721: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GitflowVersioncontrolplaysanimportantaroleanditdoesnotonlyinfluencethesourcecodemanagementprocessbutalsoshapestheentireworkflowofproductdevelopmentanddelivery.Thusasuccessfulbranchingmodelbecomesaseriouschoice.

GitflowisacollectionofGitextensionsthatprovideshigh-levelrepositoryoperationsforabranchingmodelraisedbyVincentDriessen.ThenameGitflowusuallyreferstothebranchingmodelaswell.

Inthisbranchingmodel,therearetwomainbranches:masteranddevelop,aswellasthreedifferenttypesofsupportingbranches:feature,hotfix,andrelease.

WiththehelpofGitflowextensions,wecaneasilyapplythisbranchingmodelwithouthavingtorememberandtypedetailedsequencesofcommands.Toinstall,pleasecheckouttheinstallationguideofGitflowat:https://github.com/nvie/gitflow/wiki/Installation.

BeforewecanuseGitflowtocreateandmergebranches,we'llneedtomakeaninitialization:

$gitflowinit-d

Note

Here-dstandsforusingdefaultbranchnamingconventions.Ifyouwouldliketocustomize,youmayomitthe-doptionandanswerthequestionsaboutgitflowinitcommand.

Thiswillcreatemasteranddevelopbranches(ifnotpresent)andsaveGitflow-relatedconfigurationtothelocalrepository.

Mainbranches

Thebranchingmodeldefinestwomainbranches:masteranddevelop.Thosetwobranchesexistinthelifetimeofthecurrentrepository:

Page 722: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Note

Thegraphintheprecedingshowsasimplifiedrelationshipbetweendevelopandmasterbranches.

Branchmaster:TheHEADofmasterbranchshouldalwayscontainproduction-readysourcecode.Itmeansthatnodailydevelopmentisdoneonmasterbranchinthisbranchingmodel,andonlycommitsthatarefullytestedandcanbeperformedwithafast-forwardshouldbemergedintothisbranch.Branchdevelop:TheHEADofdevelopbranchshouldcontaindelivereddevelopmentsourcecode.Changestodevelopbranchwillfinallybemergedintomaster,butusuallynotdirectly.We'llcometothatlaterwhenwetalkaboutreleasebranches.

Supportingbranches

TherearethreetypesofsupportingbranchesinthebranchingmodelofGitflow:feature,hotfix,andrelease.Whattheyroughlydohasalreadybeensuggestedbytheirnames,andwe'llhavemoredetailstofollow.

Featurebranches

Afeaturebranchhasonlydirectinteractionswiththedevelopbranch,whichmeansitchecksoutfromadevelopbranchandmergesbacktoadevelopbranch.Thefeaturebranchesmightbethesimplesttypeofbranchesoutofthethree.

TocreateafeaturebranchwithGitflow,simplyexecutethefollowingcommand:

$gitflowfeaturestart<feature-name>

NowGitflowwillautomaticallycheckoutanewbranchnamedafterfeature/<feature-name>,andyouarereadytostartdevelopmentandcommitchangesoccasionally.

Aftercompletingfeaturedevelopment,Gitflowcanautomaticallymergethingsbacktothedevelopbranchbythefollowingcommand:

$gitflowfeaturefinish<feature-name>

Afeaturebranchisusuallystartedbythedeveloperwhoisassignedtothedevelopmentofthatveryfeatureandismergedbythedeveloperhimorherself,ortheownersofthedevelopbranch(forexample,ifcodereviewisrequired).

Releasebranches

Inasingleiterationofaproduct,afterfinishingthedevelopmentoffeatures,weusuallyneedastageforfullytestingeverything,fixingbugs,andactuallygettingitreadytobereleased.Andworkforthisstagewillbedoneonreleasebranches.

Unlikefeaturebranches,arepositoryusuallyhasonlyoneactivereleasebranchatatime,and

Page 723: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

itisusuallycreatedbytheowneroftherepository.Whenthedevelopmentbranchisreachingastateofreleaseandathoroughtestisabouttobegin,wecanthencreateareleasebranchusingthefollowingcommand:

$gitflowreleasestart<version>

Fromnowon,bugfixesthataregoingtobereleasedinthisiterationshouldbemergedorcommittedtobranchrelease/<version>andchangestothecurrentreleasebranchcanbemergedbacktothedevelopbranchanytime.

Ifthetestgoeswellandimportantbugshavebeenfixed,wecanthenfinishthisreleaseandputitonline:

$gitflowreleasefinish<version>

Afterexecutingthiscommand,Gitflowwillmergethecurrentreleasebranchtobothmasteranddevelopbranches.SoinastandardGitflowbranchingmodel,thedevelopbranchwillnotbemergedintothemasterdirectly,thoughafterfinishingarelease,thecontentondevelopandmasterbranchescouldbeidentical(ifnomorechangesaremadetothedevelopbranchduringthereleasingstage).

Note

Finishingthecurrentreleaseusuallymeanstheendoftheiteration,andthedecisionshouldbemadewithseriousconsideration.

Hotfixbranches

Unfortunately,there'saphenomenonintheworldofdevelopers:bugsarealwayshardertofindbeforethecodegoeslive.Afterreleasing,ifseriousbugswerefound,wewouldhavetousehotfixestomakethingsright.

Ahotfixbranchworkskindoflikeareleasebranchbutlastsshorter(becauseyouwouldprobablywantitmergedassoonaspossible).Unlikefeaturebranchesbeingcheckedoutfromdevelopbranch,ahotfixbranchischeckedoutfrommaster.Andaftergettingthingsdone,itshouldbemergedbacktobothmasteranddevelopbranches,justlikeareleasebranchdoes.

Tocreateahotfixbranch,similarlyyoucanexecutethefollowingcommand:

$gitflowhotfixstart<hotfix-name>

Andtofinish,executethefollowingcommand:

$gitflowhotfixfinish<hotfix-name>

SummaryofGitflow

ThemostvaluableideainGitflowbesidethebranchingmodelitselfis,inmyopinion,the

Page 724: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

clearoutlineofoneiteration.YoumaynotneedtofolloweverystepmentionedthusfartouseGitflow,butjustmakeitfityourwork.Forexample,forsmallfeaturesthatcanbedoneinasinglecommit,youmightnotactuallyneedafeaturebranch.Butconversely,Gitflowmightnotbringmuchvalueiftheiterationitselfgetschaotic.

Page 725: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PullrequestbasedcodereviewCodereviewcouldbeaveryimportantjointofteamcooperation.Itensuresacceptablequalityofthecodeitselfandhelpsnewcomerscorrecttheirmisunderstandingoftheprojectandaccumulateexperiencesrapidlywithouttakingawrongpath.

Ifyouhavetriedtocontributecodetoopen-sourceprojectsonGitHub,youmustbefamiliarwithpullrequestsorPR.ThereareactuallytoolsorIDEswithcodereviewingworkflowbuilt-in.ButwithGitHubandotherself-hostedserviceslikeGitLab,wecangetitdonesmoothlywithoutrelyingonspecifictools.

Configuringbranchpermissions

Restrictionsonaccessingspecificbrancheslikemasteranddeveloparenottechnicallynecessary.Butwithoutthoserestrictions,developerscaneasilyskipcodereviewingbecausetheyarejustabletodoso.InservicesprovidedbytheVisualStudioTeamFoundationServer,wemayaddacustomcheckinpolicytoforcecodereview.ButinlighterserviceslikeGitHubandGitLab,itmightbehardertohavesimilarfunctionality.

Theeasiestwaymightbetohavedeveloperswhoaremorequalifiedandfamiliarwiththecurrentprojecthavethepermissionsforwritingthedevelopbranch,andrestrictcodereviewinginthisgroupverbally.Forotherdevelopersworkingonthisproject,pullrequestsarenowforcedforgettingchangestheymerged.

Note

GitHubrequiresanorganizationaccounttospecifypushpermissionsforbranches.Besidesthis,GitHubprovidesastatusAPIandcanaddrestrictionstomergingsothatonlybrancheswithavalidstatuscangetmerged.

Commentsandmodificationsbeforemerge

AgreatthingaboutthosepopularGitservicesisthatthereviewerandmaybeothercolleaguesofyoursmaycommentonyourpullrequestsorevenspecificlinesofcodetoraisetheirconcernsorsuggestions.Andaccordingly,youcanmakemodificationstotheactivepullrequestandmakethingsalittlebitclosertoperfect.

Furthermore,referencesbetweenissuesandpullrequestsareshownintheconversation.Thisalongwiththecommentsandmodificationrecordsmakesthecontextofcurrentpullrequestsclearandtraceable.

Page 726: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingbeforecommitsIdeally,wewouldexpecteverycommitwemaketopasstestsandcodelinting.Butbecausewearehuman,wecaneasilyforgetaboutrunningtestsbeforecommittingchanges.Andthen,ifwehavealreadysetupcontinuousintegration(we'llcometothatshortly)ofthisproject,pushingthechangeswouldmakeitred.AndifyourcolleaguehassetupaCIlightwithanalarm,youwouldmakeitflashandsoundout.

Toavoidbreakingthebuildconstantly,youmightwanttoaddapre-commithooktoyourlocalrepository.

Githooks

Gitprovidesvarietiesofhookscorrespondingtospecificphasesofanoperationoranevent.AfterinitializingaGitrepository,Gitwillcreatehooksamplesunderthedirectory.git/hooks.

Nowlet'screatethefilepre-commitunderthedirectory.git/hookswiththefollowingcontent:

#!/bin/sh

npmruntest

Note

Thehookfiledoesnothavetobeabashfile,anditcanjustbeanyexecutable.Forexample,ifyouwantliketoworkwithaNode.jshook,youcanupdatetheshebangas#!/usr/bin/envnodeandthenwritethehookinJavaScript.

AndnowGitwillruntestsbeforeeverycommitofchanges.

Addingpre-commithookautomatically

Addinghooksmanuallytothelocalrepositorycouldbetrivial,butluckilywehavenpmpackageslikepre-committhatwilladdpre-commithooksautomaticallywhenit'sinstalled(asyouusuallymightneedtorunnpminstallanyway).

Tousethepre-commitpackage,justinstallitasadevelopmentdependency:

$npminstallpre-commit--save-dev

Itwillreadyourpackage.jsonandexecutenpmscriptslistedwiththefieldpre-commitorprecommit:

{

..

"script":{

"test":"istanbulcover..."

},

Page 727: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

"pre-commit":["test"]

}

Note

Atthetimeofwriting,npmpackagepre-commitusessymboliclinkstocreateGithook,whichrequiresadministratorprivilegesonWindows.Butfailingtocreateasymboliclinkwon'tstopthenpminstallcommandfromcompleting.SoifyouareusingWindows,youprobablymightwanttoensurepre-commitisproperlyinstalled.

Page 728: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ContinuousintegrationThecontinuousintegration(CI)referstoapracticeofintegratingmultiplepartsofaprojectorsolutiontogetherregularly.Dependingonthesizeoftheproject,theintegrationcouldbetakenforeverysinglechangeoronatimedschedule.

Themaingoalofcontinuousintegrationistoavoidintegrationissues,anditalsoenforcesthedisciplineoffrequentautomatedtesting,thishelpstofindbugsearlierandpreventsthedegenerationoffunctionalities.

Therearemanysolutionsorserviceswithcontinuousintegrationsupport.Forexample,self-hostedserviceslikeTFSandJenkins,orcloud-basedserviceslikeVisualStudioTeamServices,Travis-CI,andAppVeyor.WearegoingtowalkthroughthebasicconfigurationofTravis-CIwithourdemoproject.

Page 729: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConnectingGitHubrepositorywithTravis-CIWearegoingtouseGitHubastheGitservicebehindcontinuousintegration.Firstofall,let'sgetourGitHubrepositoryandTravis-CIsettingsready:

1. CreateacorrespondentrepositoryasoriginandpushthelocalrepositorytoGitHub:

$gitremoteaddoriginhttps://github.com/<username>/<repo>.git

$gitpush-uoriginmaster

2. SignintoTravis-CIwithyourGitHubaccountat:https://travis-ci.org/auth.3. Gototheaccountpage,findtheprojectweareworkingwith,andthenflickthe

repositoryswitchon.

NowtheonlythingweneedtomakethecontinuousintegrationsetupworkisaproperTravis-CIconfigurationfile.Travis-CIhasbuilt-insupportformanylanguagesandruntimes.ItprovidesmultipleversionsofNode.jsandmakesitextremelyeasytotestNode.jsprojects.

Createthefile.travis.ymlintherootofprojectwiththefollowingcontent:

language:node_js

node_js:

-"4"

-"6"

before_script:

-npmrunbuild

ThisconfigurationfiletellsTravis-CItotestwithbothNode.jsv4andv6,andexecutethecommandnpmrunbuildbeforetesting(itwillrunthenpmtestcommandautomatically).

Almostready!Nowaddandcommitthenew.travis.ymlfileandpushittoorigin.Ifeverythinggoeswell,weshouldseeTravis-CIstartthebuildofthisprojectshortly.

Note

Youmightbeseeingbuildingstatusbadgeseverywherenowadays,andit'seasytoaddonetotheREADME.mdofyourownproject.IntheprojectpageonTravis-CI,youshouldseeabadgenexttotheprojectname.CopyitsURLandaddittotheREADME.mdasanimage:

![buildingstatus](https://api.travis-ci.org/<username>/<repo>.svg)

Page 730: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DeploymentautomationRatherthanaversioncontroltool,Gitisalsopopularforrelativelysimpledeploymentautomation.Andinthissection,we'llgetourhandsonandconfigureautomateddeploymentbasedonGit.

Page 731: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PassivedeploymentbasedonGitserversidehooksTheideaofpassivedeploymentissimple:whenaclientpushescommitstothebarerepositoryontheserver,apost-receivehookofGitwillbetriggered.Andthuswecanaddscriptscheckingoutchangesandstartdeployment.

TheelementsinvolvedintheGitdeploymentsolutiononboththeclientandserversidesincludes:

Tomakethismechanismwork,weneedtoperformthefollowingsteps:

1. Createabarerepositoryontheserverwiththefollowingcommand:

$mkdirdeployment.git

$cddeployment.git

$gitinit--bare

Note

Abarerepositoryusuallyhastheextension.gitandcanbetreatedasacentralizedplaceforsharingpurposes.Unlikenormalrepositories,abarerepositorydoesnothavetheworkingcopyofsourcefiles,anditsstructureisquitesimilartowhat'sinsidea.gitdirectoryofanormalrepository.

2. Adddeployment.gitasaremoterepositoryofourproject,andtrytopushthemasterbranchtothedeployment.gitrepository:

$cd../demo-project

$gitremoteadddeployment../deployment.git

$gitpush-udeploymentmaster

Note

Page 732: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Weareaddingalocalbarerepositoryastheremoterepositoryinthisexample.Extrastepsmightberequiredtocreaterealremoterepositories.

3. Addapost-receivehookforthedeployment.gitrepository.We'vealreadyworkedwiththeclientsideGithookpre-commit,andtheserversidehooksworkthesameway.

Butwhenitcomestoaseriousproductiondeployment,howtowritethehookcouldbeahardquestiontoanswer.Forexample,howdoweminimizetheimpactofdeployingnewbuilds?

Ifwehavesetupourapplicationwithhighavailabilityloadbalancing,itmightnotbeabigissuetohaveoneofthemofflineforminutes.Butcertainlynotalloftheminthiscase.Soherearesomebasicrequirementsofthedeployscriptsonboththeclientandserversides:

ThedeploymentshouldbeproceededinacertainsequenceThedeploymentshouldstoprunningservicesgently

Andwecandobetterby:

BuildingoutsideofthepreviousdeploymentdirectoryOnlytryingtostoprunningservicesafterthenewlydeployedapplicationisreadytostartimmediately

Page 733: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ProactivedeploymentbasedontimersornotificationsInsteadofusingGithooks,wecanhaveothertoolspullandbuildtheapplicationautomaticallyaswell.Inthisway,wenolongerneedtheclienttopushchangestoserversseparately.Andinstead,theprogramontheserverwillpullchangesfromaremoterepositoryandcompletedeployment.

Anotificationmechanismispreferredtoavoidfrequentfetchingthough,andtherearealreadytoolslikePM2thathaveautomateddeploymentbuilt-in.Youcanalsoconsiderbuildingupyourownusinghooksprovidedbycloud-basedorself-hostedGitservices.

Page 734: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthisfinalchapter,webuilttheoutlineofacompleteworkflowstartingwithbuildingandtestingtocontinuousintegrationandautomateddeployment.We'vecoveredsomepopularservicesortoolsandprovideotheroptionsforreaderstodiscoverandexplore.

Amongthevarietiesofchoice,youmightagreethatthemostappropriateworkflowforyourteamistheworkflowthatfitsthebest.Takingpeopleratherthantechnologiesaloneintoconsiderationisanimportantpartofsoftwareengineering,anditisalsothekeytokeepingtheteamefficient(andhappy,perhaps).

Thesadthingaboutateam,oracrowdofpeopleisthatusuallyonlyafewofthemcankeepthepassionburning.We’vetalkedaboutfindingthebalancepoint,butthatiswhatwestillneedtopractice.Andinmostofthecases,expectingeveryoneofyourteamtofindtherightpointisjustunreasonable.Whenitcomestoteamprojects,we'dbetterhaverulesthatcanbevalidatedautomaticallyinsteadofconventionsthatarenottestable.

Afterreadingthisbook,Ihopethereadergetstheoutlinesofthebuildsteps,workflow,andofcourseknowledgeofcommondesignpatterns.Butratherthanthecoldexplanationsofdifferenttermsandpatterns,therearemoreimportantideasIwantedtodeliver:

Weashumansaredull,andshouldalwayskeepourworkdividedascontrollablepieces,insteadofactinglikeagenius.Andthat'salsowhyweneedtodesignsoftwaretomakeourliveseasier.Andwearealsounreliable,especiallyatascaleofsomemass(likeateam).Asalearner,alwaystrytounderstandthereasonbehindaconclusionormechanismbehindaphenomenon.

Page 735: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Part3.Module3TypeScriptBlueprints

Buildexcitingend-to-endapplicationswithTypeScript

Page 736: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter1.TypeScript2.0FundamentalsInChapters2through5,wewilllearnafewframeworkstocreate(web)applicationswithTypeScript.FirstyouneedsomebasicknowledgeofTypeScript2.0.IfyouhaveusedTypeScriptpreviously,thenyoucanskimoverthischapter,oruseitasareferencewhilereadingtheotherchapters.IfyouhavenotusedTypeScriptyet,thenthischapterwillteachyouthefundamentalsofTypeScript.

Page 737: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WhatisTypeScript?TheTypeScriptlanguagelookslikeJavaScript;itisJavaScriptwithtypeannotationsaddedtoit.TheTypeScriptcompilerhastwomainfeatures:itisatranspilerandatypechecker.Atranspilerisaspecialformofcompilerthatoutputssourcecode.IncaseoftheTypeScriptcompiler,TypeScriptsourcecodeiscompiledtoJavaScriptcode.Atypecheckersearchesforcontradictionsinyourcode.Forinstance,ifyouassignastringtoavariable,andthenuseitasanumber,youwillgetatypeerror.

Thecompilercanfigureoutsometypeswithouttypeannotations;forothersyouhavetoaddtypeannotations.Anadditionaladvantageofthesetypesisthattheycanalsobeusedineditors.Aneditorcanprovidecompletionsandrefactoringbasedonthetypeinformation.EditorssuchasVisualStudioCodeandAtom(withaplugin,namelyatom-typescript)providesuchfeatures.

Page 738: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

QuickexampleThefollowingexamplecodeshowssomebasicTypeScriptusage.Ifyouunderstandthiscode,youhaveenoughknowledgeforthenextchapters.Thisexamplecodecreatesaninputboxinwhichyoucanenteraname.Whenyouclickonthebutton,youwillseeapersonalizedgreeting:

classHello{

privateelement:HTMLDivElement;

privateelementInput:HTMLInputElement;

privateelementText:HTMLDivElement;

constructor(defaultName:string){

this.element=document.createElement("div");

this.elementInput=document.createElement("input");

this.elementText=document.createElement("div");

constelementButton=document.createElement("button");

elementButton.textContent="Greet";

this.element.appendChild(this.elementInput);

this.element.appendChild(elementButton);

this.element.appendChild(this.elementText);

this.elementInput.value=defaultName;

this.greet();

elementButton.addEventListener("click",

()=>this.greet()

);

}

show(parent:HTMLElement){

parent.appendChild(this.element);

}

greet(){

this.elementText.textContent=`Hello,

${this.elementInput.value}!`;

}

}

consthello=newHello("World");

hello.show(document.body);

Theprecedingcodecreatesaclass,Hello.TheclasshasthreepropertiesthatcontainanHTMLelement.Wecreatetheseelementsintheconstructor.TypeScripthasdifferenttypesforallHTMLelementsanddocument.createElementgivesthecorrespondingelementtype.Ifyoureplacedivwithspan(onthefirstlineoftheconstructor),youwouldgetatypeerrorsayingthattypeHTMLSpanElementisnotassignabletotypeHTMLDivElement.Theclasshastwofunctions:onetoaddtheelementtotheHTMLpageandonetoupdatethegreetingbasedontheenteredname.

Page 739: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Itisnotnecessarytospecifytypesforallvariables.ThetypesofthevariableselementButtonandhellocanbeinferredbythecompiler.

Youcanseethisexampleinactionbycreatinganewdirectoryandsavingthefileasscripts.ts.Inindex.html,youmustaddthefollowingcode:

<!DOCTYPEHTML>

<html>

<head>

<title>HelloWorld</title>

</head>

<body>

<scriptsrc="scripts.js"></script>

</body>

</html>

TheTypeScriptcompilerrunsonNodeJS,whichcanbeinstalledfromhttps://nodejs.org.Afterward,youcaninstalltheTypeScriptcompilerbyrunningnpminstalltypescript-ginaconsole/terminal.Youcancompilethesourcefilebyrunningtscscripts.ts.Thiswillcreatethescripts.jsfile.Openindex.htmlinabrowsertoseetheresult.

ThenextsectionsexplainthebasicsofTypeScriptinmoredetail.Afterreadingthosesections,youshouldunderstandthisexamplefully.

Page 740: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TranspilingThecompilertranspilesTypeScripttoJavaScript.Itdoesthefollowingtransformationsonyoursourcecode:

RemovealltypeannotationsCompilenewJavaScriptfeaturesforoldversionsofJavaScriptCompileTypeScriptfeaturesthatarenotstandardJavaScript

Wecanseetheprecedingthreetransformationsinactioninthenextexample:

enumDirection{

Left,

Right,

Up,

Down

}

letx:Direction=Direction.Left;

TypeScriptcompilesthistothefollowing:

varDirection;

(function(Direction){

Direction[Direction["Left"]=0]="Left";

Direction[Direction["Right"]=1]="Right";

Direction[Direction["Up"]=2]="Up";

Direction[Direction["Down"]=3]="Down";

})(Direction||(Direction={}));

varx=Direction.Left;

Inthelastline,youcanseethatthetypeannotationwasremoved.Youcanalsoseethatletwasreplacedbyvar,sinceletisnotsupportedinolderversionsofJavaScript.Theenumdeclaration,whichisnotstandardJavaScript,wastranspiledtonormalJavaScript.

Page 741: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypecheckingThemostimportantfeatureofTypeScriptistypechecking.Forinstance,forthefollowingcode,itwillreportthatyoucannotassignanumbertoastring:

letx:string=4;

Inthenextsections,youwilllearnthenewfeaturesofthelatestJavaScriptversions.Afterward,wewilldiscussthebasicsofthetypechecker.

Page 742: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

LearningmodernJavaScriptJavaScripthasdifferentversions.SomeoftheseareES3,ES5,ES2015(alsoknownasES6),andES2016.Recentversionsarenamedaftertheyearinwhichtheywereintroduced.Dependingontheenvironmentforwhichyouwritecode,somefeaturesmightbeormightnotbesupported.TypeScriptcancompilenewfeaturesofJavaScripttoanolderversionofJavaScript.Thatisnotpossiblewithallfeatures,however.

RecentwebbrowserssupportES5andtheyareworkingonES2015.

Wewillfirsttakealookattheconstructsthatcanbetranspiledtoolderversions.

Page 743: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

letandconstES2015hasintroducedletandconst.Thesekeywordsarealternativestovar.Thesepreventissueswithscoping,asletandconstareblockscopedinsteadoffunctionscoped.Youcanusesuchvariablesonlywithintheblockinwhichtheywerecreated.Itisnotallowedtousesuchvariablesoutsideofthatblockorbeforeitsdefinition.Thefollowingexampleillustratessomedangerousbehaviorthatcouldbepreventedwithletandconst:

alert(x.substring(1,2));

varx="lorem";

for(vari=0;i<10;i++){

setTimeout(function(){

alert(i);

},10*i);

}

Thefirsttwolinesgivenoerror,asavariabledeclaredwithvarcanbeusedbeforeitsdefinition.Withletorconst,youwillgetanerror,asexpected.

Thesecondpartshows10messageboxessaying10.Wewouldexpect10messagessaying0,1,2,andsoonupto9.But,whenthecallbackisexecutedandalertiscalled,iisalready10,soyousee10messagessaying10.

Whenyouchangethevarkeywordstolet,youwillgetanerrorinthefirstlineandthemessagesworkasexpected.Thevariableiisboundtotheloopbody.Foreachiteration,itwillhaveadifferentvalue.Theforloopistranspiledasfollows:

var_loop_1=function(i){

setTimeout(function(){

alert(i);

},10*i);

};

for(vari=0;i<10;i++){

_loop_1(i);

}

Avariabledeclaredwithconstcannotbereassigned,andavariablewithletcanbereassigned.Ifyoureassignaconstvariable,yougetacompileerror.

Page 744: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ClassesAsofES2015,youcancreateclasseseasily.Inolderversions,youcouldsimulateclassestoacertainextent.TypeScripttranspilesaclassdeclarationtotheoldwaytosimulateaclass:

classPerson{

age:number;

constructor(publicname:string){

}

greet(){

console.log("Hello,"+this.name);

}

}

constperson=newPerson("World");

person.age=35;

person.greet();

Thisexampleistranspiledtothefollowing:

varPerson=(function(){

functionPerson(name){

this.name=name;

}

Person.prototype.greet=function(){

console.log("Hello,"+this.name);

};

returnPerson;

}());

varperson=newPerson("World");

person.age=35;

person.greet();

Whenyouprefixanargumentoftheconstructorwithpublicorprivate,itisaddedasapropertyoftheclass.Otherpropertiesmustbedeclaredinthebodyoftheclass.ThisisnotpertheJavaScriptspecification,butneededwithTypeScriptfortypeinformation.

Page 745: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ArrowfunctionsES6introducedanewwaytocreatefunctions.Arrowfunctionsarefunctionexpressionsdefinedusing=>.Suchfunctionlookslikethefollowing:

(x:number,y:boolean):string=>{

statements

}

Thefunctionexpressionstartswithanargumentlist,followedbyanoptionalreturntype,thearrow(=>),andthenablockwithstatements.Ifthefunctionhasonlyoneargumentwithouttypeannotationandnoreturntypeannotation,youmayomittheparenthesis:x=>{...}.Ifthebodycontainsonlyonereturnstatement,withoutanyotherstatements,youcansimplifyitto(x:number,y:number)=>expression.Afunctionwithoneargumentandonlyareturnstatementcanbesimplifiedtox=>expression.

Besidestheshortsyntax,arrowfunctionshaveoneothermajordifferencewithnormalfunctions.Arrowfunctionssharethevalueofthisandthepositionwhereitwasdefined;thisislexicallybound.Previously,youwouldstorethevalueofthisinavariablecalled_thisorself,oryouwouldfixthevalueusing.bind(this).Witharrowfunctions,thatisnotrequiredanymore.

Page 746: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionargumentsItispossibletoaddadefaultvaluetoanargument:

functionsum(a=0,b=0,c=0){

returna+b+c;

}

sum(10,5);

Whenyoucallthisfunctionwithlessthanthreearguments,itwillsettheotherargumentsto0.TypeScriptwillautomaticallyinferthetypesofa,b,andcbasedontheirdefaultvalues,soyoudonothavetoaddatypeannotationthere.

Youcanalsodefineanoptionalargumentwithoutadefaultvalue:functiona(x?:number){}.Theargumentwillthenbeundefinedwhenitisnotprovided.ThisisnotstandardJavaScript,butonlyavailableinTypeScript.

Thesumfunctioncanbedefinedevenbetter,witharestargument.Attheendofafunction,youcanaddarestargument:

functionsum(...xs:number[]){

lettotal=0;

for(leti=0;i<xs.length;i++)total+=xs[i];

returntotal;

}

sum(10,5,2,1);

Page 747: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ArrayspreadItiseasiertocreatearraysinES6.Youcancreateanarrayliteral(withbrackets),inwhichyouuseanotherarray.Inthefollowingexample,youcanseehowyoucanaddanitemtoalistandhowyoucanconcatenatetwolists:

consta=[0,1,2];

constb=[...a,3];

constc=[...a,...b];

AsimilarfeatureforobjectliteralswillprobablybeaddedtoJavaScripttoo.

Page 748: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DestructuringWithdestructuring,youcaneasilycreatevariablesforpropertiesofanobjectorelementsofanarray:

consta={x:1,y:2,z:3};

constb=[4,5,6];

const{x,y,z}=a;

const[u,v,w]=b;

Theprecedingistranspiledtothefollowing:

vara={x:1,y:2,z:3};

varb=[4,5,6];

varx=a.x,y=a.y,z=a.z;

varu=b[0],v=b[1],w=b[2];

Youcanusedestructinginanassignment,variabledeclaration,orargumentofafunctionheader.

Page 749: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TemplatestringsWithtemplatestrings,youcaneasilycreateastringwithexpressionsinit.Ifyouwouldwrite"Hello,"+name+"!",youcannowwriteHello${name}!.

Page 750: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NewclassesES2015hasintroducedsomenewclasses,includingMap,Set,WeakMap,WeakSet,andPromise.Inmodernbrowsers,theseclassesarealreadyavailable.Forotherenvironments,TypeScriptdoesnotautomaticallyaddafallbackfortheseclasses.Instead,youshoulduseapolyfill,suchases6-shim.Mostbrowsersalreadysupporttheseclasses,soinmostcases,youdonotneedapolyfill.Youcanfindinformationonbrowsersupportathttp://caniuse.com.

Page 751: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypecheckingThecompilerwillcheckthetypesofyourcode.Ithasseveralprimitivetypesandyoucandefinenewtypesyourself.Basedonthesetypes,thecompilerwillwarnwhenavalueofatypeisusedinaninvalidmanner.Thatcouldbeusingastringformultiplicationorusingapropertyofanobjectthatdoesnotexist.Thefollowingcodewouldshowtheseerrors:

letx="foo";

x*2;

x.bar();

TypeScripthasaspecialtype,calledany,thatallowseverything;youcanassigneveryvaluetoitandyouwillnevergettypeerrors.Thetypeanycanbeusedifyoudonothaveanexacttype(yet),forinstance,becauseitisacomplextypeorifitisfromalibrarythatwasnotwritteninTypeScript.Thismeansthatthefollowingcodegivesnocompileerrors:

letx:any="foo";

x*2;

x.bar();

Inthenextsections,wewilldiscoverthesetypesandlearnhowthecompilerfindsthesetypes.

Page 752: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

PrimitivetypesTypeScripthasseveralprimitivetypes,whicharelistedinthefollowingtable:

Name Values Example

boolean true,false letx:boolean=true;

string Anystringliteral letx:string="foo";

number Anynumber,includingInfinity,-Infinity,andNaN

letx:number=42;

lety:number=NaN;

Literaltypes Literaltypescanonlycontainonevalue letx:"foo"="foo";

void Onlyusedforafunctionthatdoesnotreturnavalue

functiona():void{}

never Novalues

any Allvaluesletx:any="foo";

lety:any=true;

Page 753: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DefiningtypesYoucandefineyourowntypesinvariousways:

Kind Meaning Example

Objecttype

Representsanobject,withthespecifiedproperties.Propertiesmarkedwith?areoptional.Objectscanalsohaveanindexer(forexample,likeanarray),orcallsignatures.

Objecttypescanbedefinedinline,withaclassorwithaninterfacedeclaration.

letx:{

a:boolean,

b:string,

c?:number,

[i:number]:

string

};

x={

a:true,b:"foo"

};

x[0]="foo";

UniontypeAvalueisassignabletoauniontypeifitisassignabletooneofthespecifiedtypes.Intheexample,itshouldbeastringoranumber.

letx:string|

number;

x="foo";

x=42;

Intersectiontype

Avalueisassignabletoanintersectiontypeifitisassignabletoallspecifiedtypes.

letx:{a:string}

&{b:number}=

{a:"foo",b:42};

EnumtypeAspecialnumbertype,withseveralvaluesdeclared.Thedeclaredmembersgetavalueautomatically,butyoucanalsospecifyavalue.

enumE{

X,

Y=100

}

leta:E=E.X;

Functiontype

Representsafunctionwiththespecifiedargumentsandreturntype.Optionalandrestargumentscanalsobespecified.

letf:(x:string,

y?:boolean)=>

number;

letg:(...xs:

number[])=>

number;

Tupletype Multiplevaluesareplacedinone,asanarray.letx:[string,

number];

X=["foo",42];

Page 754: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UndefinedandnullBydefault,undefinedandnullcanbeassignedtoeverytype.Thus,thecompilercannotgiveyouawarningwhenavaluecanpossiblybeundefinedornull.TypeScript2.0hasintroducedanewmode,calledstrictNullChecks,whichaddstwonewtypes:undefinedandnull.Withthatmode,youdogetwarningsinsuchcases.WewilldiscoverthatmodeinChapter6,AdvancedProgramminginTypeScript.

Page 755: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeannotationsTypeScriptcaninfersometypes.ThismeansthattheTypeScriptcompilerknowsthetype,withoutatypeannotation.Ifatypecannotbeinferred,itwilldefaulttoany.Insuchacase,orincasetheinferredtypeisnotcorrect,youhavetospecifythetypesyourself.Thecommondeclarationsthatyoucanannotatearegiveninthefollowingtable:

Location Canitbeinferred? Examples

Variabledeclaration Yes,basedoninitializer

leta:number;

letb=1;

Functionargument

Yes,basedondefaultvalue(secondexample)orwhenpassingthefunctiontoatypedvariableorfunction(thirdexample)

functiona(x:

number){}

functionb(x

=1){}

[1,2].map(

x=>x*2

);

Functionreturntype Yes,basedonreturnstatementsinbody

functiona():

number{}

():number=>

{}

functionc(){

return1;

}

Classmember Yes,basedondefaultvalue

classA{

x:number;

y=0;

}

Interfacemember No

interfaceA{

x:number;

}

YoucansetthecompileroptionnoImplicitAnytogetcompilererrorswhenatypecouldnotbeinferredandfallsbacktoany.Itisadvisedtousethatoptionalways,unlessyouaremigratingaJavaScriptcodebasetoTypeScript.YoucanreadaboutsuchmigrationinChapter10,MigrateJavaScripttoTypeScript.

Page 756: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,youdiscoveredthebasicsofTypeScript.YoushouldnowbefamiliarwiththeprinciplesofTypeScriptandyoushouldunderstandthecodeexampleatthebeginningofthechapter.Younowhavetheknowledgetostartwiththenextchapters,inwhichyouwilllearntwomajorwebframeworks,Angular2andReact.WewillstartwithAngular2inChapter2,AWeatherForecastWidgetwithAngular2.

Page 757: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter2.AWeatherForecastWidgetwithAngular2Inthischapter,we'llcreateasimpleapplicationthatshowsustheweatherforecast.Theframeworkweuse,Angular2,isanewframeworkwrittenbyGoogleinTypeScript.Theapplicationwillshowtheweatherofthecurrentdayandthenext.Inthefollowingscreenshot,youcanseetheresult.WewillexploresomekeyconceptsofAngular,suchasdatabindinganddirectives.

Wewillbuildtheapplicationinthefollowingsteps:

UsingmodulesSettinguptheprojectCreatingthefirstcomponentAddingconditionstothetemplateShowingaforecastCreatingtheforecastcomponentsThemaincomponent

Page 758: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingmodulesWewillusemodulesinallapplicationsinthisbook.Modules(alsocalledexternalmodulesandES2015modules)areaconceptofseparatingcodeinmultiplefiles.Everyfileisamodule.Withinthesemodules,youcanusevariables,functions,andclasses(members)exportedbyothermodulesandyoucanmakesomemembersvisibleforothermodules.Touseothermodules,youmustimportthem,andtomakemembersvisible,youneedtoexportthem.Thefollowingexamplewillshowsomebasicusage:

//x.ts

import{one,add,Lorem}from'./y';

console.log(add(one,2));

varlorem=newLorem();

console.log(lorem.name);

//y.ts

exportvarone=1;

exportfunctionadd(a:number,b:number){

returna+b;

}

exportclassLorem{

name="ipsum";

}

Youcanexportdeclarationsbyprefixingthemwiththeexportkeywordorbyprefixingthemwithexportdefault.Adefaultexportshouldbeimporteddifferentlythoughwewillnotusesuchanexportasitcanbeconfusing.Therearevariouswaystoimportafile.Wehaveseenthevariantthatisusedmosttimes,import{a,b,c}from'./d'.Thedotandslashmeanthatthed.tsfileislocatedinthesamedirectory.Youcanuse./x/yand../ztoreferenceafileinasubdirectoryoraparentdirectory.Areferencethatdoesnotstartwithadotcanbeusedtoimportalibrary,suchasAngular.Anotherimportvariantisimport*asefrom'./d'.Thiswillimportallexportsfromd.ts.Theseareavailablease.a,e.b,eisanobjectthatcontainsallexports.

Tokeepcodereadableandmaintainable,itisadvisabletousemultiplesmallfilesinsteadofonebigfile.

Page 759: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SettinguptheprojectWewillquicklysetuptheprojectbeforewecanstartwriting.Wewillusenpmtomanageourdependenciesandgulptobuildourproject.ThesetoolsarebuiltonNodeJS,soitshouldbeinstalledfromnodejs.org.

Firstofall,wemustcreateanewdirectoryinwhichwewillplaceallfiles.Wemustcreateapackage.jsonfileusedbynpm:

{

"name":"weather-widget",

"version":"1.0.0",

"private":true,

"description":""

}

Thepackage.jsonfilecontainsinformationabouttheproject,suchasthename,version,andadescription.ThesefieldsareusedbynpmwhenyoupublishaprojectontheregistryonNPM,whichcontainsalotofopensourceprojects.Wewillnotpublishitthere.Wesettheprivatefieldtotrue,sowecannotaccidentallypublishit.

Page 760: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DirectorystructureWewillseparatetheTypeScriptsourcesfromtheotherfiles.TheTypeScriptfileswillbeaddedinthelibdirectory.Staticfiles,suchasHTMLandCSS,willbelocatedinthestaticdirectory.Thisdirectorycanbeuploadedtoawebserver.Thecompiledsourceswillbewrittentostatic/scripts.WefirstinstallAngularandsomerequirementsofAngularwithnpm.Inaterminal,werunthefollowingcommandintherootdirectoryoftheproject:

npminstallangular2rxjses6-shimreflect-metadatazone.js--save

Theconsolemightshowsomewarningsaboutunmetpeerdependencies.ThesewillprobablybecausedbyaminorversionmismatchbetweenAngularandoneofitsdependencies.Youcanignorethesewarnings.

Page 761: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringTypeScriptTypeScriptcanbeconfiguredusingatsconfig.jsonfile.Wewillplacethatfileinthelibdirectory,asallourfilesarelocatedthere.WespecifytheexperimentalDecoratorsandemitDecoratorMetadataoptions,asthesearenecessaryforAngular:

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"experimentalDecorators":true,

"emitDecoratorMetadata":true,

"lib":["es2015","dom"]

}

}

ThetargetoptionspecifiestheversionofJavaScriptofthegeneratedcode.Currentbrowserssupportes5.TypeScriptwillcompilenewerJavaScriptfeatures,suchasclasses,toanes5equivalent.Withtheliboption,wecanspecifytheversionoftheJavaScriptlibrary.Weusethelibrariesfromes2015,theversionafteres5.Sincetheselibrariesmightnotbeavailableinallbrowsers,wewilladdapolyfillforthesefeatureslateron.WealsoincludethelibrariesfortheDOM,whichcontainsfunctionssuchasdocument.createElementanddocument.getElementById.

Page 762: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BuildingthesystemWithgulp,itiseasytocompileaprograminmultiplesteps.Formostwebapps,multiplestepsareneeded:compilingTypeScript,bundlingmodules,andfinallyminifyingallcode.Inthisapplication,weneedtodoallofthesesteps.

Gulpstreamssourcefilesthroughaseriesofplugins.Thesepluginscan(justlikegulpitself)beinstalledusingnpm:

npminstallgulp--global

npminstallgulpgulp-typescriptgulp-sourcemapsgulp-uglifysmall--save-dev

Tip

The--globalflagwillinstallthedependencygloballysuchthatyoucancallgulpfromaterminal.The--save-devflagwilladdthedependencytothedevDependencies(developmentdependencies)sectionofthepackage.jsonfile.Use--savetoaddaruntimedependency.

Weusethefollowingpluginsforgulp:

Thegulp-typescriptplugincompilesTypeScripttoJavaScriptThegulp-uglifyplugincanminifyJavaScriptfilesThesmallplugincanbundleexternalmodulesThegulp-sourcemapspluginimprovesthedebuggingexperiencewithsourcemaps

Wewillcreatetwotasks,onethatcompilesthesourcestoadevelopmentbuildandanotherthatcancreateareleasebuild.Thedevelopmentbuildwillhavesourcemapsandwillnotbeminified,whereasthereleasebuildwillbeminifiedwithoutsourcemaps.Minifyingtakessometimesowedonotdothatonthedebugtask.Creatingsourcemapsinthereleasetaskispossibletoo,butgeneratingthesourcemapisslowsowewillnotdothat.

Wewritethesetasksingulpfile.jsintherootoftheproject.Thesecondtaskistheeasiesttowrite,asitonlyusesoneplugin.Thetaskwilllooklikethis:

vargulp=require('gulp');

varuglify=require('gulp-uglify');

gulp.task('release',['compile'],function(){

returngulp.src('static/scripts/scripts.js')

.pipe(uglify())

.pipe(gulp.dest('static/scripts'));

});

Thegulp.taskcallwillregisteratasknamedrelease,whichwilltakestatic/scripts/scripts.js(whichwillbecreatedbythecompiletask),runuglify(atoolthatminifiesJavaScript)onit,andthensaveitinthesamedirectoryagain.Thistaskdependsonthecompiletask,meaningthatthecompiletaskwillberunbeforethisone.

Page 763: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thefirsttask,compile,ismorecomplicated.ThetaskwilltranspileTypeScript,andbundlethefileswiththeexternallibraries.

First,wemustloadsomeplugins:

vargulp=require('gulp');

vartypescript=require('gulp-typescript');

varsmall=require('small').gulp;

varsourcemaps=require('gulp-sourcemaps');

varuglify=require('gulp-uglify');

WeloadtheconfigurationofTypeScriptinthetsconfig.jsonfile:

vartsProject=typescript.createProject('lib/tsconfig.json');

Now,wecanfinallywritethetask.First,weloadallsourcesandcompilethemusingtheTypeScriptcompiler.Afterthat,webundlethesefiles(includingAngular,storedundernode_modules,usingsmall):

gulp.task('compile',function(){

returngulp.src('lib/**/*.ts')

.pipe(sourcemaps.init())

.pipe(typescript(tsProject))

.pipe(small('index.js',{

externalResolve:['node_modules'],

globalModules:{

"crypto":{

standalone:"undefined"

}

}

}))

.pipe(sourcemaps.write('.'))

.pipe(gulp.dest('static/scripts'));

});

gulp.task('release',['compile'],function(){

returngulp.src('static/scripts/scripts.js')

.pipe(uglify())

.pipe(gulp.dest('static/scripts'));

});

gulp.task('default',['compile']);

Thistaskcompilesourprojectandsavestheresultasstatic/scripts/scripts.js.Thesourcemaps.init()andsourcemaps.write('.')functionshandlethecreationofsourcemaps,whichwillimprovethedebuggingexperience.

Page 764: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TheHTMLfileThemainfileofourapplicationistheHTMLfile,static/index.html.Thisfilewillreferenceour(compiled)scriptsandstylesheet:

<!DOCTYPEHTML>

<html>

<head>

<title>Weather</title>

<linkrel="stylesheet"href="style.css"/>

</head>

<body>

<divid="wrapper">

<weather-widget>Loading..</weather-widget>

</div>

<scriptsrc="scripts/index.js"type="text/javascript"></script>

</body>

</html>

Theweather-widgettagwillbeinitializedbyAngular.Wewilladdsomefancystylesinstatic/style.css:

body{

font-family:'SegoeUI',Tahoma,Geneva,Verdana,sans-serif;

font-weight:100;

}

h1,h2,h3{

font-weight:100;

margin:0;

padding:0;

color:#57BEDE;

}

#wrapper{

position:absolute;

left:0;

right:0;

top:0;

width:450px;

margin:10%auto;

}

a:link,a:visited{

color:#57BEDE;

text-decoration:underline;

}

a:hover,a:active{

color:#44A4C2;

}

.clearfix{

clear:both;

}

Page 765: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthefirstcomponentAngularisbasedoncomponents.ComponentsarebuiltwithothercomponentsandnormalHTMLtags.Ourapplicationwillhavethreecomponents:theforecastpage,theaboutpage,andthewholewidget.Thewidgetitself,whichisreferencedintheHTMLpage,willusetheothertwowidgets.

ThewidgetwillshowtheAboutpageinthethirdtab,asyoucanseeinthefollowingscreenshot:

Theforecastcomponentisshowninthefirsttabofthefollowingscreenshot.Wewillcreatetheforecastandthewidgetlaterinthischapter.

Page 766: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThetemplateAcomponentisaclassdecoratedwithsomemetadata.Decoratorsarefunctionsthatcanmodifyaclassordecorateitwithsomemetadata.Asimplecomponentthatdoesnothaveanyinteractionwilllooklikethis:

import{Component}from"angular2/core";

@Component({

selector:"about-page",

template:`

<h2>About</h2>

ThiswidgetshowstheweatherforecastofUtrecht.

Thenext24hoursareshownunder'Today'andtheforecastof24-48

hoursaheadunder'Tomorrow'.

`

})

exportclassAbout{

}

Tip

Asaconvention,youcanalwayschooseselectornameswithadash(-).Youcanthenidentifycomponentsbythedash.NormalHTMLtagswillneverhavenameswithadash.

Thiscomponentwillbetheaboutpageselectorofourapplication.Wewillmodifyitinthenextsessions.Wewilluseonefilepercomponent,sowesavethisaslib/about.ts.

Page 767: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingWecantestthecomponentbycallingthebootstrapfunction.Wecreateanewfile,lib/index.ts,whichwillstarttheapplication:

import"zone.js";

import"rxjs";

import"reflect-metadata";

import"es6-shim";

import{bootstrap}from"angular2/platform/browser";

import{About}from"./about";

bootstrap(About).catch(err=>console.error(err));

Tip

The.catchsectionwillshowerrorsintheconsole.Ifyoudonotincludethatcall,youwillnotseethoseerrorsandthatcanbeprettyfrustrating.

Wemustchangetheweather-widgettaginstatic/index.htmltoanabout-pagetag.Now,wecanrungulpandopenindex.htmlinabrowsertoseetheresults.

Atthetimeofwritingthis,whenyourunthiscommand,yougetanerrorwhensayingthatthetypedefinitionofzone.jsisincorrect.Youcanignorethiserrorasitisabugofzone.js.

Tip

Testearly

It'salwaysagoodideatotestduringdevelopment.Ifyoutestafterwritingalotofcode,youwilldiscoverissueslate,anditwilltakemoreworktorepairthem.Everytimethatyouwanttotesttheproject,youmustfirstrungulpandthenopenorrefreshindex.html.

Page 768: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

InteractionsWecanaddaninteractioninsidetheclassbody.Wemustusebindingstoconnectthetemplatetodefinitionsinthebody.Therearethreedifferentbindings:

One-wayvariablebindingOne-wayeventlistenerTwo-waybinding

Aone-waybindingwillconnecttheclassbodyandtemplateinonedirection.Incaseofavariable,changesofthevariablewillupdatethetemplate,butthetemplatecannotupdatethevariable.Atemplatecanonlysendaneventtotheclass.Incaseofatwo-waybinding,achangeofthevariablechangesthetemplateandachangeinthetemplatewillchangethevariable.Thisisusefulforthevalueofaninputelement,forexample.Wewilltakealookatone-waybindingsinthenextsection.

Page 769: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

One-wayvariablebindingInthefirstattemptoftheaboutpage,thelocation(Utrecht)ishardcoded.Inthefinalapplication,wewanttochooseourownlocation.Thefirststepwewilltakeistoaddapropertytotheclassthatcontainsthelocation.Usingaone-waybinding,wewillreferencethatvalueinthetemplate.Aone-wayvariablebindingisdenotedwithbracketsinsideattributesanddoublecurlybracketsinsidetext:

import{Component}from"angular2/core";

@Component({

selector:"about-page",

template:`

<h2>About</h2>

Thiswidgetshowstheweatherforecastof

<a[href]="'https://maps.google.com/?q='+encodedLocation">

{{location}}

</a>

Thenext24hoursareshownunder'Today'andtheforecastof24-48

hoursaheadunder'Tomorrow'.

`

})

exportclassAbout{

location="Utrecht";

getencodedLocation(){

returnencodeURIComponent(this.location);

}

}

Tip

Atthetimeofwritingthis,templatesaren'tcheckedbyTypeScript.Makesurethatyouwritethecorrectnamesofthevariables.Variablesshouldnotbeprefixedbythis.,likeyouwoulddoinclassmethods.

Youcanaddanexpressioninsuchbindings.Inthisexample,thebindingofthehrefattributedoesstringconcatenation.However,thesubsetofexpressionsislimited.Youcanaddmorecomplexcodeinsidegettersintheclass,asdonewithencodedLocation.

Tip

Youcanalsouseadifferentgetter,whichwouldencodethelocationandconcatenateitwiththeGoogleMapsURL.

Page 770: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

EventlistenersEventbindingscanconnectaneventemitterofatagorcomponenttoamethodofafunction.Suchbindingisdenotedwithparenthesisinthetemplate.Wewilladdashow-morebuttontoourapplication:

import{Component}from"angular2/core";

@Component({

selector:"about-page",

template:`

<h2>About</h2>

Thiswidgetshowstheweatherforecastof

<a[href]="'https://maps.google.com/?q='+encodedLocation">

{{location}}

</a>.

Thenext24hoursareshownunder'Today'andtheforecastof24-48

hoursaheadunder'Tomorrow'.

<br/>

<ahref="javascript:;"(click)="show()">Showmore</a>

<ahref="javascript:;"(click)="hide()">Showless</a>

`

})

exportclassAbout{

location="Utrecht";

collapsed=true;

show(){

this.collapsed=false;

}

hide()

{

this.collapsed=true;

}

getencodedLocation(){

returnencodeURIComponent(this.location);

}

}

Theshow()orhide()functionwillbecalledwhenoneoftheshoworhidelinksisclickedon.

Page 771: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingconditionstothetemplateTheeventhandlerintheprevioussectionsetsthepropertycollapsedtofalsebutthatdoesnotmodifythetemplate.Innormalcode,wewouldhavewrittenif(this.collapsed){...}.Intemplates,wecannotusethat,butwecanusengIf.

Page 772: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DirectivesAdirectiveisanextensiontonormalHTMLtagsandattributes.Itcandefinecustombehavior.Acustomcomponent,suchastheAboutpage,canbeseenasadirectivetoo.ThengIfconditionisabuilt-indirectiveinAngular.Itisacustomattributethatdisplaysthecontentifthespecifiedvalueistrue.

Page 773: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThetemplatetagIfapieceofacomponentneedstobeshownavariableanamountoftimes,youcanwrapitinatemplatetag.UsingthengIf(orngFor)directive,youcancontrolhowoftenitisshown(incaseofngIf,onceorzerotimes).Thetemplatetagwilllooklikethis:

<template[ngIf]="collapsed">

<div>Content</div>

</template>

Youcanabbreviatethisasfollows:

<div*ngIf="collapsed">Content</div>

Itisadvisedtousetheabbreviatedstyle,butit'sgoodtorememberthatitisshorthandforthetemplatetag.

Page 774: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ModifyingtheabouttemplateSincengIfisabuilt-indirective,itdoesn'thavetobeimported.Customdirectivesneedtobeimported.Wewillseeanexampleofusingcustomcomponentslaterinthischapter.Inthetemplate,wecanuse*ngIfnow.Thetemplatewillthuslooklikethis:

template:`

<h2>About</h2>

Thiswidgetshowstheweatherforecastof

<a[href]="'https://maps.google.com/?q='+encodedLocation">

{{location}}

</a>.

Thenext24hoursareshownunder'Today'andtheforecastof24-48

hoursaheadunder'Tomorrow'.

<br/>

<a*ngIf="collapsed"href="javascript:;"(click)="show()">Show

more</a>

<div*ngIf="!collapsed">

Theforecastusesdatafrom<a

href="http://openweathermap.org">OpenWeatherMap</a>.

<br/>

<ahref="javascript:;"(click)="hide()">Hide</a>

</div>

`

})

Theclassbodydoesnothavetobechanged.Asyoucansee,youcanuseexpressionsinthe*ngIfbindings,whichisnotsurprisingasitisashorthandforone-wayvariablebindings.

Page 775: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingthecomponentinothercomponentsWecanusetheabout-pagecomponentinothercomponents,asifitwasanormalHTMLtag.Butthecomponentisstillboring,asitwillalwayssaythatitshowstheweatherbroadcastofUtrecht.Wecanmarkthelocationpropertyasaninput.Afterthat,locationisanattributethatwecansetfromothercomponents.Itisevenpossibletobinditasaone-waybinding.TheInputdecorator,whichweareusinghere,needstobeimportedjustlikeComponent:

import{Component,Input}from"angular2/core";

@Component({

...

})

exportclassAbout{

@Input()

location:string="Utrecht";

collapsed=true;

show(){

this.collapsed=false;

}

hide(){

this.collapsed=true;

}

getencodedLocation(){

returnencodeURIComponent(this.location);

}

}

Page 776: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ShowingaforecastWestillhavenotshownaforecastyet.Wewillusedatafromopenweathermap(http://www.openweathermap.org).Youcancreateanaccountontheirwebsite.Withyouraccount,youcanrequestanAPItoken.Youneedthetokentorequesttheforecast.Afreeaccountislimitedto60requestspersecondand50,000requestsperday.

WesavetheAPItokeninaseparatefile,lib/config.ts:

exportconstopenWeatherMapKey="your-token-here";

exportconstapiURL="http://api.openweathermap.org/data/2.5/";

Tip

Addconstantstoaseparatefile

Whenyouaddconstantsinseparateconfigurationfiles,youcaneasilychangethemandyourcodeismorereadable.Thisgivesyoubettermaintainablecode.

Page 777: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingtheAPIWewillcreateanewfile,lib/api.ts,thatwillsimplifydownloadingdatafromopenweathermap.TheAPIusesURLssuchashttp://api.openweathermap.org/data/2.5/forecast?mode=json&q=Utrecht,NL&appid=your-token-here.WewillcreateafunctionthatwillbuildthefullURLoutofforecast?mode=json&q=Utrecht,NL.Thefunctionmustcheckwhetherthepathalreadycontainsaquestionmark.Ifso,itmustadd&appid=,otherwise?appid=:

import{openWeatherMapKey,apiURL}from"./config";

exportfunctiongetUrl(path:string){

leturl=apiURL+path;

if(path.indexOf("?")===-1){

url+="?";

}else{

url+="&";

}

url+="appid="+openWeatherMapKey;

returnurl;

}

Tip

Writesmallfunctions

Smallfunctionsareeasytoreuse.Thisreducestheamountofcodeyouneedtowrite.Thesameappliestocomponents—smallcomponentsareeasytoreuse.

Page 778: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypingtheAPIYoucanopentheURLintheprevioussectiontogetalookatthedatayouget.WewillwriteaninterfaceforthepartoftheAPIthatwewilluse:

exportinterfaceForecastResponse{

city:{

name:string;

country:string;

};

list:ForecastItem[];

}

exportinterfaceForecastItem{

dt:number;

main:{

temp:number

};

weather:{

main:string,

description:string

};

}

Tip

JSDoccomments

YoucanadddocumentationforinterfacesandtheirpropertiesbyaddingaJSDoccommentbeforeit:

/***Documentationhere*/

Page 779: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtheforecastcomponentAsaquickrecap,theforecastwidgetwilllooklikethis:

Whatpropertiesdoestheclassneed?Thetemplatewillneedforecastdataofthecurrentdayorthenextday.ThecomponentcanshowtheweatherofTodayandTomorrow,sowewillalsoneedapropertyforthat.Forfetchingtheforecast,wealsoneedthelocation.Toshowtheloadingstateinthetemplate,wewillalsostorethatintheclass.Thiswillresultinthefollowingclass,inlib/forecast.ts:

import{Component,Input}from"angular2/core";

import{ForecastResponse}from"./api";

exportinterfaceForecastData{

date:string;

temperature:number;

main:string;

description:string;

}

enumState{

Loading,

Refreshing,

Loaded,

Error

}

@Component({

selector:"weather-forecast",

template:`...`

})

exportclassForecast{

temperatureUnit="degreesCelsius";

@Input()

tomorrow=false;

@Input()

location="Utrecht";

data:ForecastData[]=[];

state=State.Loading;

}

Tip

Page 780: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Testing

Youcantestthiscomponentbyadjustingthetaginindex.htmlandbootstrappingtherightcomponentinindex.ts.Rungulptocompilethesourcesandopenthewebbrowser.

Page 781: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TemplatesThetemplateusesthengFordirectivetoiterateoverthedataarray:

import{Component,Input}from"angular2/core";

import{ForecastResponse}from"./api";

...

@Component({

selector:"weather-forecast",

template:`

<span*ngIf="loading"class="state">Loading...</span>

<span*ngIf="refreshing"class="state">Refreshing...</span>

<a*ngIf="loaded||error"href="javascript:;"(click)="load()"

class="state">Refresh</a>

<h2>{{tomorrow?'Tomorrow':'Today'}}'sweatherin{{location}}

</h2>

<div*ngIf="error">Failedtoloaddata.</div>

<ul>

<li*ngFor="#itemofdata">

<divclass="item-date">{{item.date}}</div>

<divclass="item-main">{{item.main}}</div>

<divclass="item-description">{{item.description}}</div>

<divclass="item-temperature">

{{item.temperature}}{{temperatureUnit}}

</div>

</li>

</ul>

<divclass="clearfix"></div>

`,

Usingthestylesproperty,wecanaddniceCSSstyles,asshownhere:

styles:[

`.state{

float:right;

margin-top:6px;

}

ul{

margin:0;

padding:0015px;

list-style:none;

width:100%;

overflow-x:scroll;

white-space:nowrap;

}

li{

display:inline-block;

margin-right:15px;

width:170px;

white-space:initial;

}

.item-date{

font-size:15pt;

Page 782: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

color:#165366;

margin-right:10px;

display:inline-block;

}

.item-main{

font-size:15pt;

display:inline-block;

}

.item-description{

border-top:1pxsolid#44A4C2;

width:100%;

font-size:11pt;

}

.item-temperature{

font-size:11pt;

}`

]

})

Intheclassbody,weaddthegetterswhichweusedinthetemplate:

exportclassForecast{

...

state=State.Loading;

getloading(){

returnthis.state===State.Loading;

}

getrefreshing(){

returnthis.state===State.Refreshing;

}

getloaded(){

returnthis.state===State.Loaded;

}

geterror(){

returnthis.state===State.Error;

}

...

}

Tip

Enums

Enumsarejustnumberswithnamesattachedtothem.It'smorereadabletowriteState.Loadedthan2,buttheymeanthesameinthiscontext.

Asyoucansee,thesyntaxofngForis*ngFor="#variableofarray".Theenumcannotbereferencedfromthetemplate,soweneedtoaddgettersinthebodyoftheclass.

Page 783: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DownloadingtheforecastTodownloaddatafromtheInternetinAngular,weneedtogettheHTTPservice.WeneedtosettheviewProviderssectionforthat:

import{Component,Input}from"angular2/core";

import{Http,Response,HTTP_PROVIDERS}from"angular2/http";

import{getUrl,ForecastResponse}from"./api";

...

@Component({

selector:"weather-forecast",

viewProviders:[HTTP_PROVIDERS],

template:`...`,

styles:[...]

})

exportclassForecast{

constructor(privatehttp:Http){

}

...

AngularwillinjecttheHttpserviceintotheconstructor.

Tip

Byincludingprivateorpublicbeforeanargumentoftheconstructor,thatargumentwillbecomeapropertyoftheclass,initializedbythevalueoftheargument.

Wewillnowimplementtheloadfunction,whichwilltrytodownloadtheforecastonthespecifiedlocation.Thefunctioncanalsousecoordinatesasalocation,writtenasCoordinateslatlon,wherelatandlonarethecoordinatesasshownhere:

privateload(){

letpath="forecast?mode=json&";

conststart="coordinate";

if(this.location&&

this.location.substring(0,

start.length).toLowerCase()===start){

constcoordinate=this.location.split("");

path+=`lat=${parseFloat(coordinate[1])}&lon=${

parseFloat(coordinate[2])}`;

}else{

path+="q="+this.location;

}

this.state=this.state===State.Loaded?

State.Refreshing:State.Loading;

this.http.get(getUrl(path))

.map(response=>response.json())

.subscribe(res=>

Page 784: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.update(<ForecastResponse>res),()

=>this.showError());

};

Tip

Threekindsofvariables

Youcandefinevariableswithconst,let,andvar.Avariabledeclaredwithconstcannotbemodified.Variablesdeclaredwithconstorletareblock-scopedandcannotbeusedbeforetheirdefinition.Avariabledeclaredwithvarisfunctionscopedandcanbeusedbeforeitsdefinition.Suchvariablecangiveunexpectedbehavior,soit'sadvisedtouseconstorlet.

ThefunctionwillfirstcalculatetheURL,thensetthestateandfinallyfetchthedataandgetreturnsanobservable.Anobservable,comparabletoapromise,issomethingthatcontainsavaluethatcanchangelateron.Likewitharrays,youcanmapanobservabletoadifferentobservable.Subscriberegistersacallback,whichiscalledwhentheobservableischanged.Thisobservablechangesonlyonce,whenthedataisloaded.Ifsomethinggoeswrong,thesecondcallbackwillbecalled.

Tip

Lambdaexpressions(inlinefunctions)

Thefatarrow(=>)createsanewfunction.It'salmostequaltoafunctiondefinedwiththefunctionkeyword(function(){return...}),butitisscopedlexically,whichmeansthatthisreferstothevalueofthisoutsidethefunction.x=>expressionisashorthandfor(x)=>expression,whichisashorthandfor(x)=>{returnexpression;}.TypeScriptwillautomaticallyinferthetypeoftheargumentbasedonthesignatureofmapandsubscribe.

Asyoucansee,thisfunctionusestheupdateandshowErrorfunctions.TheupdatefunctionstorestheresultsoftheopenweathermapAPI,andshowErrorisasmallfunctionthatsetsthestatetoState.Error.SincetemperaturesoftheAPIareexpressedinKelvin,wemustsubstract273togetthevalueinCelsius:

fullData:ForecastData[]=[];

data:ForecastData[]=[];

privateformatDate(date:Date){

returndate.getHours()+":"

+date.getMinutes()+":"

+date.getSeconds();

}

privateupdate(data:ForecastResponse){

if(!data.list){

this.showError();

return;

}

this.fullData=data.list.map(item=>({

Page 785: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

date:this.formatDate(newDate(item.dt*1000)),

temperature:Math.round(item.main.temp-273),

main:item.weather[0].main,

description:item.weather[0].description

}));

this.filterData();

this.state=State.Loaded;

}

privateshowError(){

this.data=[];

this.state=State.Error;

}

privatefilterData(){

conststart=this.tomorrow?8:0;

this.data=this.fullData.slice(start,start+8);

}

ThefilterDatamethodwillfiltertheforecastbasedonwhetherwewanttoseetheforecastoftodayortomorrow.Openweathermaphasoneforecastper3hours,so8perday.Theslicefunctionwillreturnasectionofthearray.fullDatawillcontainthefullforecast,sowecaneasilyshowtheforecastoftomorrow,ifwehavealreadyshowntoday.

Tip

Changedetection

Angularwillautomaticallyreloadthetemplatewhensomepropertyischanged,there'snoneedtoinvalidateanything(asC#developersmightexpect).Thisiscalledchangedetection.

Wealsowanttorefreshdatawhenthelocationischanged.Iftomorrowischanged,wedonotneedtodownloadanydata,becausewecanjustuseadifferentsectionofthefullDataarray.Todothat,wewillusegettersandsetters.Inthesetter,wecandetectchanges:

private_tomorrow=false;

@Input()

settomorrow(value){

if(this._tomorrow===value)return;

this._tomorrow=value;

this.filterData();

}

gettomorrow(){

returnthis._tomorrow;

}

private_location:string;

@Input()

setlocation(value){

if(this._location===value)return;

this._location=value;

this.state=State.Loading;

this.data=[];

this.load();

Page 786: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

getlocation(){

returnthis._location;

}

Page 787: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Adding@OutputTheresponseofOpenweathermapcontainsthenameofthecity.Wecanusethistosimulatecompletionlateron.Wewillcreateaneventemitter.Othercomponentscanlistentotheeventandupdatethelocationwhentheeventistriggered.Thewholecodewilllooklikethiswithfinalchangeshighlighted:

import{Component,Input,Output,EventEmitter}from"angular2/core";

import{Http,Response,HTTP_PROVIDERS}from"angular2/http";

import{getUrl,ForecastResponse}from"./api";

interfaceForecastData{

date:string;

temperature:number;

main:string;

description:string;

}

enumState{

Loading,

Refreshing,

Loaded,

Error

}

@Component({

selector:"weather-forecast",

viewProviders:[HTTP_PROVIDERS],

template:`

<span*ngIf="loading"class="state">Loading...</span>

<span*ngIf="refreshing"class="state">Refreshing...</span>

<a*ngIf="loaded||error"href="javascript:;"(click)="load()"

class="state">Refresh</a>

<h2>{{tomorrow?'Tomorrow':'Today'}}'sweatherin{{location}}

</h2>

<div*ngIf="error">Failedtoloaddata.</div>

<ul>

<li*ngFor="#itemofdata">

<divclass="item-date">{{item.date}}</div>

<divclass="item-main">{{item.main}}</div>

<divclass="item-description">{{item.description}}</div>

<divclass="item-temperature">

{{item.temperature}}{{temperatureUnit}}

</div>

</li>

</ul>

<divclass="clearfix;"></div>

`,

styles:[

`.state{

float:right;

margin-top:6px;

}

ul{

Page 788: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

margin:0;

padding:0015px;

list-style:none;

width:100%;

overflow-x:scroll;

white-space:nowrap;

}

li{

display:inline-block;

margin-right:15px;

width:170px;

white-space:initial;

}

.item-date{

font-size:15pt;

color:#165366;

margin-right:10px;

display:inline-block;

}

.item-main{

font-size:15pt;

display:inline-block;

}

.item-description{

border-top:1pxsolid#44A4C2;

width:100%;

font-size:11pt;

}

.item-temperature{

font-size:11pt;

}`

]

})

exportclassForecast{

constructor(privatehttp:Http){

}

temperatureUnit="degreesCelsius";

private_tomorrow=false;

@Input()

settomorrow(value){

if(this._tomorrow===value)return;

this._tomorrow=value;

this.filterData();

}

gettomorrow(){

returnthis._tomorrow;

}

private_location:string;

@Input()

setlocation(value){

if(this._location===value)return;

this._location=value;

Page 789: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

this.state=State.Loading;

this.data=[];

this.load();

}

getlocation(){

returnthis._location;

}

fullData:ForecastData[]=[];

data:ForecastData[]=[];

state=State.Loading;

getloading(){

returnthis.state===State.Loading;

}

getrefreshing(){

returnthis.state===State.Refreshing;

}

getloaded(){

returnthis.state===State.Loaded;

}

geterror(){

returnthis.state===State.Error;

}

@Output()

correctLocation=newEventEmitter<string>(true);

privateformatDate(date:Date){

returndate.getHours()+":"+date.getMinutes()+date.getSeconds();

}

privateupdate(data:ForecastResponse){

if(!data.list){

this.showError();

return;

}

constlocation=data.city.name+","+data.city.country;

if(this._location!==location){

this._location=location;

this.correctLocation.next(location);

}

this.fullData=data.list.map(item=>({

date:this.formatDate(newDate(item.dt*1000)),

temperature:Math.round(item.main.temp-273),

main:item.weather[0].main,

description:item.weather[0].description

}));

this.filterData();

this.state=State.Loaded;

Page 790: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

privateshowError(){

this.data=[];

this.state=State.Error;

}

privatefilterData(){

conststart=this.tomorrow?8:0;

this.data=this.fullData.slice(start,start+8);

}

privateload(){

letpath="forecast?mode=json&";

conststart="coordinate";

if(this.location&&this.location.substring(0,

start.length).toLowerCase()===start){

constcoordinate=this.location.split("");

path+=`lat=${parseFloat(coordinate[1])}&lon=${

parseFloat(coordinate[2])}`;

}else{

path+="q="+this.location;

}

this.state=this.state===State.Loaded?State.Refreshing:

State.Loading;

this.http.get(getUrl(path))

.map(response=>response.json()))

.subscribe(res=>this.update(<ForecastResponse>res),()=>

this.showError());

}

}

Tip

Thegeneric(typeargument)innewEventEmitter<string>()meansthatthecontentsofaneventwillbeastring.Ifthegenericisnotspecified,itdefaultsto{},anemptyobjecttype,whichmeansthatthereisnocontent.Inthiscase,wewanttosendthenewlocation,whichisastring.

Page 791: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThemaincomponentAsyoucanseeinthescreenshotintheintroductionofthischapter,thiscomponentshouldhaveatextbox,abutton,andthreetabs.Underthetabs,thesecomponentwillshowtheforecastortheAboutpage.

Page 792: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingourothercomponentsWecanuseourcomponentsthatwehavealreadywrittenbyaddingthemtothedirectivessectionandusingtheirtagnamesinthetemplate.

Page 793: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Two-waybindingsTogetthevalueoftheinputbox,weneedtwo-waybindings.WecanusethengModeldirectiveforthat.Thesyntaxcombinesthesyntaxesofthetwoone-waybindings:[(ngModel)]="property".Thedirectiveisagainabuilt-inone,sowedon'thavetoimportit.

Usingthistwo-waybinding,wecanautomaticallyupdatetheweatherwidgetaftereverykeypress.Thatwouldcausealotofrequeststotheserver,andespeciallyonslowconnections,that'snotdesired.

Topreventtheseissues,wewilladdtwoseparateproperties.ThepropertylocationwillcontainthecontentoftheinputandactiveLocationwillcontainthelocation,whichisbeingshown.

Page 794: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ListeningtooureventWecanlistentoourevent,justlikewedidwithotherevents.Wecanaccesstheeventcontentwith$event.Suchalistenerwilllooklike(correct-location)="correctLocation($event).Whentheserverrespondswiththeforecast,italsoprovidesthenameofthelocation.Iftheuserhadasmalltypointhename,theresponsewillcorrectthat.Thiseventwillbefiredinsuchacaseandthenamewillbecorrectedintheinputbox.

Page 795: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GeolocationAPIBecauseourforecastwidgetsupportscoordinates,wecanusethegeolocationAPItosettheinitiallocation.ThatAPIcangivethecoordinateswherethedeviceislocated(roughly).Lateron,wewillusethisAPItosetthewidgettothecurrentlocationwhenthepageloadsasshownhere:

navigator.geolocation.getCurrentPosition(position=>{

constlocation=`Coordinate${position.coords.latitude}${

position.coords.longitude}`;

this.location=location;

this.activeLocation=location;

});

Tip

Templatestring

Templatestrings,nottobeconfusedwithAngulartemplates,arestringswrappedinbackticks(`).Thesestringscanbemultilineandcancontainexpressionsbetween${and}.

Page 796: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ComponentsourcesAsusual,westartbyimportingAngular.Wealsohavetoimportthetwocomponentswehavealreadywritten.Weuseanenumerationagaintostorethestateofthecomponent:

import{Component}from"angular2/core";

import{Forecast}from"./forecast";

import{About}from"./about";

enumState{

Today,

Tomorrow,

About

}

Thetemplatewillusethetwo-waybindingontheinputelement:

@Component({

selector:"weather-widget",

directives:[Forecast,About],

template:`

<input[(ngModel)]="location"(keyup.enter)="clickGo()"

(blur)="clickGo()"/>

<button(click)="clickGo()">Go</button>

<divclass="tabs">

<ahref="javascript:;"[class.selected]="selectedTab===0"

(click)="selectTab(0)">Today</a>

<ahref="javascript:;"[class.selected]="selectedTab===1"

(click)="selectTab(1)">Tomorrow</a>

<ahref="javascript:;"[class.selected]="selectedTab===2"

(click)="selectTab(2)">About</a>

</div>

<divclass="content"[class.is-dirty]="isDirty"*ngIf="selectedTab===

0||selectedTab===1">

<weather-forecast[location]="activeLocation"

[tomorrow]="selectedTab===1"(correctLocation)="correctLocation($event)"

/>

</div>

<divclass="content"*ngIf="selectedTab===2">

<about-page[location]="activeLocation"/>

</div>

`,

Bindingtoclass.selectedmeansthattheelementwillhavetheselectedclassiftheboundvalueistrue.Afterthetemplate,wecanaddsomestylesasshownhere:

styles:[

`.tabs>a{

display:inline-block;

padding:5px;

margin-top:5px;

border:1pxsolid#57BEDE;

Page 797: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

border-bottom:0pxnone;

text-decoration:none;

}

.tabs>a.selected{

background-color:#57BEDE;

color:#fff;

}

.content{

border-top:5pxsolid#57BEDE;

}

.is-dirty{

opacity:0.4;

background-color:#ddd;

}`

]

})

Intheconstructor,wecanusethegeolocationAPItogetthecoordinatesofthecurrentposition:

exportclassWidget{

constructor(){

navigator.geolocation.getCurrentPosition(position=>{

constlocation=`Coordinate${position.coords.latitude}${

position.coords.longitude}`;

this.location=location;

this.activeLocation=location;

});

}

location:string="Utrecht,NL";

activeLocation:string="Utrecht,NL";

getisDirty(){

returnthis.location!==this.activeLocation;

}

clickGo(){

this.activeLocation=this.location;

}

correctLocation(location:string){

if(!this.isDirty)this.location=location;

this.activeLocation=location;

}

selectedTab=0;

selectTab(index:number){

this.selectedTab=index;

}

}

Page 798: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wecreatedanapplicationwithAngular2.WeexploredAngular2anduseditsdirectivesandbindingsinourcomponents.WealsousedanonlineAPI.YoushouldnowbeabletobuildsmallAngularapplications.Inthenextchapter,wewillbuildamorecomplexapplicationinAngular,whichwillalsouseitsownserver.

Page 799: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter3.Note-TakingAppwithaServerInthischapter,wewillcreateaclient-serverapp.TheclientwillbewrittenusingAngular2andtheserverwillbewrittenusingNodeJSandMongoDB.WecanuseTypeScriptonbothsidesandwewillseehowwecanreusecodebetweenthem.

Theapplicationcanbeusedtotakenotes.Wewillimplementaloginpageandbasic,Create,Read,Update,andDelete(CRUD)operationsforthenotes.

Inthischapter,wewillcoverthefollowingtopics:

SettinguptheprojectstructureGettingstartedwithNodeJSUnderstandingthestructuraltypesystemAddingauthenticationTestingtheAPIAddingCRUDoperationsWritingtheclientsideRunningtheapplication

Page 800: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SettinguptheprojectstructureFirst,wehavetosetuptheproject.Thedifferencewiththepreviouschapteristhatwenowhavetobuildtwoapplications—theclientsideandtheserverside.Thiscausessomedifferenceswiththeprevioussetup.

Page 801: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DirectoriesWewillagainplaceourTypeScriptsourcesinthelibdirectory.Inthatdirectory,wewillcreatefoursubdirectories:client,server,shared,andtypings.Thelib/clientdirectorywillcontaintheclient-sideapplicationandthelib/serverdirectorywillcontaintheservercode.Codesthatcanbeusedbyboththeserverandtheclientwillgoinlib/shared.Lastbutnotleast,lib/typingswillcontaintypedefinitionsforsomedependencies,includingNodeJS.

Page 802: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringthebuildtoolInlib,wecreateatsconfig.jsonfilethatwillcontainsomeconfigurationforTypeScript.Wewanttocompiletheserver-sidecodetoes2015,sowecanusesomenewfeaturesofTypeScriptandJavaScript.Theclientside,however,mustbecompiledtoes5forbrowsersupport.Inthetsconfigfile,wewillspecifyes2015astargetandoverrideitinthegulpfile.Wecanalsospecifytheversionofthedefaultlibrarythatwewanttouse.Weneedes2015anddom.ThefirstcontainstherecentclassesandfunctionsfromJavaScript,suchasMapandObject.assign:

{

"compilerOptions":{

"target":"es6",

"module":"commonjs",

"experimentalDecorators":true,

"emitDecoratorMetadata":true,

"lib":["es2015","dom"]

}

}

Theliboptionwillonlymakethetypesfornewclassesandfunctionsavailable.Atruntime,thesemightnotbepresent.Weincludeapolyfill,es6-shim,tomakesurethatthesewillalwaysbeavailable.

Thegulpfile,locatedintherootoftheproject,iscomparabletotheconfigurationofthepreviouschapter.Wecaninstallallnecessarydependencies,includingruntimedependencies,usingnpm:

npminit

npminstallgulpgulp-typescriptsmallgulp-sourcemapsmerge2gulp-concat

gulp-uglify--save-dev

npminstallangular2es6-shimrxjsphaethon--save

Youcanagainsettheprivatepropertyinpackage.jsonsothatyoudon'taccidentallyuploadyourprojecttonpm.Ingulpfile.js,wecannowloadalldependencies:

vargulp=require("gulp");

vartypescript=require("gulp-typescript");

varsmall=require("small").gulp;

varsourcemaps=require("gulp-sourcemaps");

varmerge=require("merge2");

varconcat=require("gulp-concat");

varuglify=require("gulp-uglify");

WewillcreatetwoTypeScriptprojects:onefortheserverandonefortheclientside.Inthesecondproject,wewilloverridethetargettoes5:

vartsServer=typescript.createProject("lib/tsconfig.json");

vartsClient=typescript.createProject("lib/tsconfig.json",{

target:"es5"

});

Page 803: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Nowwecanusealmostthesametaskasinthepreviouschapter.Thesourcesmustbeloadedfromlib/clientinsteadoflib,andlib/sharedshouldbeincludedtoo:

gulp.task("compile-client",function(){

returngulp.src(["lib/client/**/*.ts","lib/shared/**/*.ts"],{base:

"lib"})

.pipe(sourcemaps.init())

.pipe(typescript(tsClient))

.pipe(small('client/index.js',{

outputFileName:{standalone:"scripts.js"},

externalResolve:['node_modules'],

globalModules:{

"crypto":{

standalone:"undefined"

}

}

}))

.pipe(sourcemaps.write('.'))

.pipe(gulp.dest('static/scripts'));

});

Thecompilationoftheserver-sidecodeissimpler,asthecodedoesn'thavetobebundled.NodeJShasabuilt-inmoduleloader:

gulp.task("compile-server",function(){

returngulp.src(["lib/server/**/*.ts","lib/shared/**/*.ts"],{base:"lib"})

.pipe(sourcemaps.init())

.pipe(typescript(tsServer))

.pipe(sourcemaps.write("."))

.pipe(gulp.dest("dist"));

});

Weaddthereleaseanddefaulttasksthatcanbuildthereleaseanddebugtasks:

gulp.task("release",["compile-client","compile-server"],function(){

returngulp.src("static/scripts/scripts.js")

.pipe(uglify())

.pipe(gulp.dest("static/scripts"));

});

gulp.task("default",["compile-client","compile-server"]);

Thetaskscanbestartedusinggulporgulprelease.

Page 804: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypedefinitionsBeforealibrarycanbeusedinTypeScript,youhavetohavetypedefinitionsforit.Thesearestoredin.d.tsfiles.Forsomepackages,theseareautomaticallyinstalled.Forexample,weusedAngularinthepreviouschapterandwedidn'tinstallthedefinitionsmanually.Packagesdistributedonnpmcanincludetheirtypedefinitionsinthesamepackage.Whenyoudownloadsuchapackage,thetypingscomealong.Unfortunately,notallpackagesdothis.AsofTypeScript2.0,itispossibletodownloadtypingsforthesepackagesonnpmtoo.Forinstance,thetypingsformongodbarepublishedinthe@types/mongodbpackage.Youcaninstalltypesforalotofpackagesthisway.TypesforNodeJSitselfareavailablein@types/node.Runthesecommandsintherootdirectory:

npminstall@types/node--save

npminstall@types/mongodb--save

Thecompilerwillautomaticallyfindthetypesformongodbwhenyouimportit.SincewewillnotexplicitlyimportNodeJSinthecode,thecompilerwillnotfindit.Wemustaddittoourtsconfigfile.

{

"compilerOptions":{

"target":"es6",

"module":"commonjs",

"experimentalDecorators":true,

"emitDecoratorMetadata":true,

"lib":["es2015","dom"],

"types":["node"]

}

}

Thecompilercannowusealltypedefinitions.

Page 805: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GettingstartedwithNodeJSInthepreviouschapter,weusedNodeJS,asgulpusesit.Nodecanbeusedforaserverandforacommandlinetool.Inthischapter,wewillbuildaserverandinChapter9,PlayingTic-Tac-ToeagainstanAI,wewillcreateacommandlineapplication.Ifyouhaven'tinstalledNodeyet,youcandownloaditfromnodejs.org.

Wewillfirstcreateasimpleserver.WewillusePhaethon,apackageforNodethatmakesiteasytobuildaserverinNodeJS.Phaethonincludestypedefinitions,sowecanuseitimmediately.Wecreateafilelib/server/index.tsandaddthefollowing:

import{Server}from"phaethon";

constserver=newServer();

server.listener=request=>newphaethon.ServerResponse("Hello");

server.listenHttp(8800);

Wecanrunthisserverusingthefollowingcommand:

gulp&&nodedist/server

Whenyouopenlocalhost:8800inawebbrowser,thelistenercallbackwillbecalledandyouwillseeHellointhebrowser.

Page 806: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AsynchronouscodeAserverdoesn'tdoalltheworkitself.Itwillalsodelegatesometasks.Forinstance,itmightneedtodownloadawebpageorfetchsomethingfromadatabase.Suchataskwillnotgivearesultimmediately.Inthemeantime,theservercoulddosomethingelse.Thisstyleofprogrammingiscalledasynchronousornonblocking,astheorderofexecutionisnotfixedandsuchtaskdoesnotblocktherestoftheapplication.

Imaginewehaveataskthatwilldownloadawebpage.Thesynchronousvariantwouldlooklikethefollowing:

functiondownload(){

return...;

}

functiondemo(){

//Beforedownload

try{

constresult=download();

constresult2=download();

//Downloadcompleted

}catch(error){

//Error

}

}

Callbackapproachforasynchronouscode

Inawebserver,thiswouldpreventtheserverfromhandlingotherrequests.Thetaskblocksthewholeserver.Thatis,ofcourse,notwhatwewant.Thesimplestasynchronousapproachusescallbacks.Thefirstargumentofthecallbackwillcontainanerrorifsomethingwentwrongandthesecondargumentwillcontaintheresultifthereisaresult:

functiondownload(callback:(error:any,result:string)=>void){

...

}

functiondemo(){

//Beforedownload

download((error,result)=>{

if(error){

//Error

}else{

//Downloadcompleted

download((error2,result2)=>{

if(error2){

}else{

//Download2completed

}

});

}

Page 807: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

});

}

Disadvantagesofcallbacks

Thedisadvantageofthisisthatwhenyouhavealotofcallbacks,youhavetonestcallbacksincallbacks,whichiscalledcallbackhell.InES6,anewclasswasintroduced,thatactslikeanabstractionofsuchatask.Itiscalledapromise.Suchavaluepromisesthattherewillbearesultnoworlateron.Thepromisecanberesolved,whichmeansthattheresultisready.Thepromisecanalsoberejected,whichmeansthattherewassomeerror:

functiondownload():Promise<string>{

...

}

functiondemo(){

//Beforedownload

download().then(result=>{

//Downloadcompleted

returndownload();

}).then(result2=>{

//Seconddownloadcompleted

});

}

Asyoucansee,theprecedingcodeismorereadablethanthecallbackscode.It'salsoeasiertochaintaskssinceyoucanreturnanotherpromiseinthethensectionofapromise.However,thesynchronouscodeisstillmorereadable.ES7hasintroducedasyncfunctions.Thesefunctionsaresyntacticsugararoundpromises.Insteadofcallingthenonapromise,youcanawaititandwritecodeasifitweresynchronous.

Tip

Atthetimeofwriting,asyncfunctionscanonlybecompiledtoES6.TypeScript2.1willintroducesupportforES5too.

functiondownload():Promise<string>{

...

}

asyncfunctiondemo(){

try{

constresult=awaitdownload();

constresult2=awaitdownload();

}catch(error){

}

}

Asyoucansee,thisisalmostthesameasthecodewestartedwith.Thisgivesthebestofbothworlds:itresultsinreadableandperformantcode.

Page 808: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Tip

Donotforgettheasynckeywordinthefunctionheader.Ifyouwanttoannotatethefunctionwithareturntype,writePromise<T>insteadofT.

Page 809: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThedatabaseAlotofprogrammersuseMongoDBincombinationwithNodeJS.YoucaninstallMongoDBfromwww.mongodb.org.MongoDBcanbestartedusingthefollowingcommandintheprojectroot:

mongod--dbpath./data

YoucankeeptheprecedingcommandrunninginoneterminalwindowandrunNodeJSinanotherterminalwindowlateron.

Wrappingfunctionsinpromises

Wewillrunthedatabaseonthesamecomputerastheserverandwewillnamethedatabasenotes.ThisyieldstheURLmongodb://localhost/notes,whichweneedtoconnecttothedatabase.Wehavealreadyinstalledthedefinitionswithtsd.MongoDBexposesanAPIbasedoncallbacks.Wewillwraptheseinpromises,aswewilluseasync/awaitlateron.Wrappingafunctioninapromisewilllooklikethefollowing:

functionwrapped(){

returnnewPromise<string>((resolve,reject)=>{

originalFunction((error,result)=>{

if(error){

reject(error);

}else{

resolve(result);

}

});

});

}

ThePromiseconstructortakesacallbackfunction.Thisfunctioncancalltheresolvecallbackifeverythingsucceededorcalltherejectfunctionifsomethingfailed.

Connectingtothedatabase

Weaddthefollowinginlib/server/database.ts.Firstwemustconnecttothedatabase.Insteadofrejectingwhentheconnectionfailed,wewillthrowtheerror.Thiswaytheserverwillquitifitcan'tconnecttothedatabase:

import{MongoClient,Db,Collection}from"mongodb";

constdatabaseUrl="mongodb://localhost:27017/notes";

constdatabase=newPromise<Db>(resolve=>{

MongoClient.connect(databaseUrl,(error,db)=>{

if(error){

throwerror;

}

resolve(db);

})

});

Page 810: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Tip

Usually,youwouldrejectthepromiseincaseofanerror.Here,wethrowtheerrorandcrashtheserver.Inthiscaseitisbettersincetheservercannotdoanythingwithoutadatabaseconnection.

Thedatabasecontainstwocollections(tables):usersandnotes.Sincewecanonlyaccesstheseaftertheconnectiontothedatabasehassucceeded,theseshouldalsobeplacedinaPromise.SincedatabasealreadyisaPromise,wecanuseasync/await:

asyncfunctiongetCollection(name:string){

constdb=awaitdatabase;

returndb.collection(name);

}

exportconstusers=getCollection("users");

exportconstnotes=getCollection("notes");

TheusersandnotesvariableshavethetypePromise<Collection>.

Wecannowwriteafunctionthatwillinsertanitemintoacollectionandreturnapromise.Sincethispromisedoesn'thavearesultingvalue,wewilltypeitasPromise<void>:

exportfunctioninsert(table:Promise<Collection>,item:any){

constcollection=awaittable;

returnnewPromise<void>((resolve,reject)=>{

collection.insertOne(item,(error)=>{

if(error){

reject(error);

}else{

resolve();

}

});

});

}

Queryingthedatabase

Toquerythedatabase,wewillusethefunctionfind.MongoDBreturnsacursorobject,whichallowsyoutostreamallresults.Ifyouhaveabigapplication,andqueriesthatreturnalotofresults,thiscanimprovetheperformanceofyourapplication.Insteadofstreamingtheresults,wecanalsobuffertheminanarraywiththetoArrayfunction:

exportfunctionfind(table:Promise<Collection>,query:any){

constcollection=awaittable;

returnnewPromise<U[]>((resolve,reject)=>{

collection.find(query,(error,cursor)=>{

if(error){

reject(error);

}else{

cursor.toArray((error,results)=>{

if(error){

reject(error);

Page 811: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}else{

resolve(results);

}

});

}

});

});

}

Wewilladdupdateandremovefunctionslateron.

Page 812: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UnderstandingthestructuraltypesystemTypeScriptusesastructuraltypesystem.Whatthatmeanscanbeeasilydemonstratedusingthefollowingexample:

classPerson{

name:string;

}

classCity{

name:string;

}

constx:City=newPerson();

InlanguageslikeC#,thiswouldnotcompile.Theselanguagesuseanominaltypesystem.Basedonthename,aPersonisnotaCity.TypeScriptusesastructuraltypesystem.BasedonthestructureofPersonandCity,thesetypesareequal,astheybothhaveanameproperty.ThisfitswellinthedynamicnatureofJavaScript.Itcan,however,leadtosomeunexpectedbehavior,asthefollowingwouldcompile:

classFoo{

}

constf:Foo=42;

SinceFoodoesnothaveanyproperties,everyvaluewouldbeassignabletoit.Incaseswerethestructuralbehaviorisnotdesired,youcanaddabrand,apropertythataddstypesafetybutdoesnotexistatruntime:

classFoo{

__fooBrand:void;

}

constf:Foo=42;

Nowthelastlinewillgiveanerror,asexpected.

Page 813: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GenericsThetypingsforMongoDBdon'tusegenericsortypearguments.Giventhatwealreadyhavetoaddatinywrapperaroundit,wecanalsoeasilyaddgenericstothatwrapper.Wewillcreateanewtypeforthedatastorethathasgenerics:

exportinterfaceTable<T>extendsCollection{

__tableBrand:T;

}

Ifyoudin'tincludethebrand,Table<A>wouldbestructurallyidenticaltoTable<B>,whichwedonotwant.Wecannowloadthecollectionswiththecorrecttypes.WeusetheUserandNotetypeshere.Wewillcreatetheseinterfaceslateron:

import{User}from"./user";

import{Note}from"./note";

asyncfunctiongetCollection<U>(name:string){

constdb=awaitdatabase;

return<Table<U>>db.collection(name);

}

exportconstusers=getCollection<User>("users");

exportconstnotes=getCollection<Note>("notes");

Withgenerics,theinsertfunctionwilllooklikethefollowing:

exportfunctioninsert<U>(table:Table<U>,item:U){

returnnewPromise<void>((resolve,reject)=>{

table.insertOne(item,(error)=>{

if(error){

reject(error);

}else{

resolve();

}

});

});

}

Forfind,wewantthequerytobeasupertypeofthetablecontent.Inotherwords,youwanttoqueryonsomepropertiesofthecontentofthetable.SupportforthiswasaddedinTypeScript1.8:

exportfunctionfind<UextendsV,V>(table:Table<U>,query:V){

returnnewPromise<U[]>((resolve,reject)=>{

table.find(query,(error,result)=>{

if(error){

reject(error);

}else{

resolve(result);

}

});

});

}

Page 814: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wewillalsowritewrappersforupdateandremove.TogetherthesefunctionscandotheCRUDoperations:create,read,update,anddelete:

exportfunctionupdate<UextendsV,V>(table:Table<U>,query:V,newItem:U){

returnnewPromise<void>((resolve,reject)=>{

table.update(query,newItem,(error)=>{

if(error){

reject(error);

}else{

resolve();

}

});

});

}

exportfunctionremove<UextendsV,V>(table:Table<U>,query:V){

returnnewPromise<void>((resolve,reject)=>{

table.remove(query,(error)=>{

if(error){

reject(error);

}else{

resolve();

}

});

});

}

Inlib/server/user.ts,wewillcreatetheUsermodel.ForMongoDB,suchtypesshouldhavean_idproperty.Thedatabasewillusethatpropertytoidentifyinstancesofthemodels:

import{ObjectID}from"mongodb";

exportinterfaceUser{

_id:ObjectID;

username:string;

passwordHash:string;

}

Andinlib/server/note.ts,weaddtheNotemodel:

import{ObjectID}from"mongodb";

exportinterfaceNote{

_id:ObjectID;

userId:string;

content:string;

}

Page 815: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypingtheAPIInlib/shared/api.ts,wewilladdsometypingsfortheAPI.Ontheserverside,wecancheckthattheresponsehastherighttype:

exportinterfaceLoginResult{

ok:boolean;

message?:string;

}

exportinterfaceMenuResult{

items:MenuItem[];

}

exportinterfaceMenuItem{

id:string;

title:string;

}

exportinterfaceItemResult{

id:string;

content:string;

}

Wewillnowimplementthefunctionsthatreturnthesetypes.

Page 816: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingauthenticationInlib/server/index.ts,wewillfirstaddsessions.Asessionisaplacetostoredata,whichispersistentforaclientontheserver.Ontheclientside,acookiewillbesaved,whichcontainsanidentifierofthesession.Ifarequestcontainsavalidcookiewithsuchanidentifier,youwillgetthesamesessionobject.Otherwise,anewsessionwillbecreated:

import{Server,ServerRequest,ServerResponse,ServerError,StatusCode,

SessionStore}from"phaethon";

import{ObjectID}from"mongodb";

import{User,login,logout}from"./user";

import*asnotefrom"./note";

Tip

Withimport{...},wecanimportasetofentitiesfromanotherfile.Withimport*as...,weimportthewholefileasanobject.Thefollowingtwosnippetsareequivalent:import*asfoofrom"./foo";foo.bar();import{bar}from"./foo";bar();

Wedefinethetypeofthecontentofthesessionasfollows:

exportinterfaceSession{

userId:ObjectID;

}

constserver=newServer();

ThesessionswillbestoredinaSessionStore.Thelifetimeofasessionis60*60*24secondsoroneday:

constsessionStore=newSessionStore<Session>("session-id",()=>({userId:

undefined}),60*60*24,1024);

server.listener=sessionStore.wrapListener(async(request,session)=>{

constresponse=awaithandleRequest(request,session.data);

if(responseinstanceofServerResponse){

returnresponse;

}else{

constserverResponse=newServerResponse(JSON.stringify(response));

returnserverResponse;

}

});

server.listenHttp(8800);

JSON.stringifywillconvertanobjecttoastring.Suchastringcaneasilybeconvertedbacktoanobjectontheclientside.InChapter2,WeatherForecastWidget,theresponsesoftheweatherAPIwerealsoformattedasJSONstrings.

InhandleRequest,allrequestswillbesenttoahandlerbasedontheirpath:

asyncfunctionhandleRequest(request:ServerRequest,session:Session):

Promise<ServerResponse|Object>{

Page 817: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

constpath=request.path.toLowerCase();

if(path==="/api/login")returnlogin(request,session);

if(path==="/api/logout")returnlogout(request,session);

thrownewServerError(StatusCode.ClientErrorNotFound);

}

Page 818: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingusersinthedatabaseNowwecanimplementauthenticationinuser.ts.Forsafety,wewon'tstoreplainpasswordsinourdatabase.Insteadwehashthem.Ahashisamanipulationofaninput,inawaythatyoucannotfindtheinputbasedonthehash.Whensomeonewantstologin,thepasswordishashedandcomparedwiththehashedpasswordfromthedatabase.Usingthebuilt-inmodulecrypto,thiscaneasilybedone:

import*ascryptofrom"crypto";

functiongetPasswordHash(username:string,password:string):string{

returncrypto.createHash("sha256").update(password.length+"-"+username

+"-"+password).digest("hex");

}

Thelogouthandleriseasytowrite.WemustremovetheuserIdofthesessionasfollows:

exportfunctionlogout(request:ServerRequest,session:Session):LoginResult{

session.userId=undefined;

return{ok:true};

}

Asyoucansee,weareusingtheLoginResultinterfacethatwewrotepreviously.Theloginfunctionwillusetheasync/awaitsyntax.ThefunctionexpectsthattheusernameandpasswordareavailableintheURLquery.Iftheyarenotavailable,validate.expectwillthrowanerror,whichwillbedisplayedasaBadRequesterror:

exportasyncfunctionlogin(request:ServerRequest,session:Session):

Promise<LoginResult>{

constusername=validate.expect(request.query["username"],

validate.isString);

constpassword=validate.expect(request.query["password"],

validate.isString);

constpasswordHash=getPasswordHash(username,password);

constresults=awaitfind(users,{username,passwordHash});

if(results.length===0){

return{ok:false,message:"Usernameorpasswordincorrect"};

}

constuser=results[0];

session.userId=user._id;

return{ok:true};

}

Page 819: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddinguserstothedatabaseToaddsomeuserstothedatabase,wemustaddsomecodeandruntheserveroncewithit.Inareal-worldapplication,youwouldprobablywanttoaddaregisterform.Thatiscomparabletoaddinganote,whichwewilldolateroninthischapter.

Wewillalsoaddtwohelperfunctionsthatwecanuseinnote.tstocheckwhethertheuserisloggedin:

import*ascryptofrom"crypto";

import{ServerRequest,ServerResponse,ServerError,StatusCode,validate}from

"phaethon";

import{Session}from"./index";

import{LoginResult}from"../shared/api";

import{users,find,insert}from"./database";

exportinterfaceUser{

_id:string;

username:string;

passwordHash:string;

}

functiongetPasswordHash(username:string,password:string):string{

returncrypto.createHash("sha256").update(password.length+"-"+

username+"-"+password).digest("hex");

}

insert(users,{

_id:undefined,

username:"lorem",

passwordHash:getPasswordHash("lorem","ipsum")

});

insert(users,{

_id:undefined,

username:"foo",

passwordHash:getPasswordHash("foo","bar")

});

exportasyncfunctionlogin(request:ServerRequest,session:Session):

Promise<LoginResult>{

constusername=validate.expect(

request.query["username"],validate.isString);

constpassword=validate.expect(

request.query["password"],validate.isString);

constpasswordHash=getPasswordHash(username,password);

constresults=awaitfind(users,{username,passwordHash});

if(results.length===0){

return{ok:false,message:"Usernameorpasswordincorrect"};

}

constuser=results[0];

session.userId=user._id;

return{ok:true};

}

Page 820: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

exportfunctionlogout(request:ServerRequest,session:Session):LoginResult

{

session.userId=undefined;

return{ok:true};

}

exportasyncfunctiongetUser(session:Session){

if(session.userId===undefined)returnundefined;

constresults=awaitfind(users,{_id:session.userId});

returnresults[0];

}

exportasyncfunctiongetUserOrError(session:Session){

constuser=awaitgetUser(session);

if(user===undefined){

thrownewServerError(StatusCode.ClientErrorUnauthorized);

}

returnuser;

}

Runtheserveronceandremovethetwoinsertcallsafterward.

Page 821: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheAPIWecanstarttheserverbyrunningthefollowingcommand:

gulp&&node--harmony_destructuringdist/server

Inawebbrowser,youcanopenlocalhost:8800/api/login?username=lorem&password=ipsumtotestthecode.Youcanchangetheparameterstotesthowawrongusernameorpasswordbehaves.

Fordebugging,youcanaddconsole.log("...");callsinyourcode.

Page 822: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingCRUDoperationsMostservershandleCRUDoperationsprimarily.Ourservermusthandlefivedifferentrequests:listallnotesofthecurrentuser,findaspecificnote,insertanewnote,updateanote,andremoveanote.

First,weaddahelperfunctionthatcanbeusedontheserversideandtheclientside.Inlib/shared/note.ts,weaddafunctionthatreturnsthetitleofanote—thefirstline,ifavailable,or"Untitled":

exportfunctiongetTitle(content:string){

constlineEnd=content.indexOf("\n");

if(content===""||lineEnd===0){

return"Untitled";

}

if(lineEnd===-1){

//Notecontainsoneline

returncontent;

}

//Getfirstline

returncontent.substring(0,lineEnd);

}

WewritetheCRUDfunctionsinlib/server/note.ts.WestartwithimportsandtheNotedefinition:

import{ServerRequest,ServerResponse,ServerError,StatusCode,validate}from

"phaethon";

import{ObjectID}from"mongodb";

import{Session}from"./index";

import{getUserOrError}from"./user";

import{Note}from"./note";

import{getTitle}from"../shared/note";

import{MenuResult,ItemResult}from"../shared/api";

import*asdatabasefrom"./database";

exportinterfaceNote{

_id:string;

userId:string;

content:string;

}

Page 823: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingthehandlersNowwecanimplementthelistfunction.Usingthehelperfunctionswewrotepreviously,wecaneasilywritethefollowingfunction:

exportasyncfunctionlist(request:ServerRequest,session:Session):

Promise<MenuResult>{

constuser=awaitgetUserOrError(session);

constresults=awaitdatabase.find(

database.notes,{userId:user._id});

constitems=results.map(note=>({

id:note._id.toHexString(),

title:getTitle(note.content)

}));

return{items};

}

WithtoHexString,anObjectIDcanbeconvertedtoastring.ItcanbeconvertedbackusingnewObjectID(...).Themapfunctiontransformsanarraywithaspecificcallback.

Inthefindfunction,wemustsearchforanotebasedonaspecificID:

exportasyncfunctionfind(request:ServerRequest,session:Session):

Promise<ItemResult>{

constuser=awaitgetUserOrError(session);

constid=validate.expect(

request.query["id"],validate.isString);

constnotes=awaitdatabase.find(database.notes,

{_id:newObjectID(id),userId:user._id});

if(notes.length===0){

thrownewServerError(StatusCode.ClientErrorNotFound);

}

constnote=notes[0];

return{

id:note._id.toHexString(),

content:note.content

};

}

Tip

DonotforgettoaddtheuserIdinthequery.Otherwise,ahackercouldfindnotesofadifferentuserwithoutknowinghis/herpassword.

Theinsert,update,andremovefunctionscanbeimplementedasfollows.Ininsert,weset_idtoundefined,asMongoDBwilladdauniqueIDitself:

exportasyncfunctioninsert(request:ServerRequest,session:Session):

Promise<ItemResult>{

constuser=awaitgetUserOrError(session);

constcontent=validate.expect(

request.query["content"],validate.isString);

constnote:Note={

Page 824: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

_id:undefined,

userId:user._id,

content

};

awaitdatabase.insert(database.notes,note);

return{

id:note._id.toHexString(),

content:note.content

};

}

exportasyncfunctionupdate(request:ServerRequest,session:Session):

Promise<ItemResult>{

constuser=awaitgetUserOrError(session);

constid=validate.expect(

request.query["id"],validate.isString);

constcontent=validate.expect(

request.query["content"],validate.isString);

constnote:Note={

_id:newObjectID(id),

userId:user._id,

content

};

awaitdatabase.update(database.notes,

{_id:newObjectID(id),userId:user._id},note);

return{

id:note._id.toHexString(),

content:note.content

};

}

exportasyncfunctionremove(request:ServerRequest,session:Session){

constuser=awaitgetUserOrError(session);

constid=validate.expect(

request.query["id"],validate.isString);

awaitdatabase.remove(database.notes,

{_id:newObjectID(id),userId:user._id});

return{};

}

Page 825: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RequesthandlingInlib/server/index.ts,wemustaddreferencestothesefunctionsinhandleRequest:

asyncfunctionhandleRequest(request:ServerRequest,session:Session):

Promise<ServerResponse|Object>{

constpath=request.path.toLowerCase();

if(path==="/api/login")

returnlogin(request,session);

if(path==="/api/logout")

returnlogout(request,session);

if(path==="/api/note/list")

returnnote.list(request,session);

if(path==="/api/note/insert")

returnnote.insert(request,session);

if(path==="/api/note/update")

returnnote.update(request,session);

if(path==="/api/note/remove")

returnnote.remove(request,session);

if(path==="/api/note/find")

returnnote.find(request,session);

thrownewServerError(StatusCode.ClientErrorNotFound);

}

Page 826: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WritingtheclientsideJustliketheweatherwidget,wewillwritetheclientsideofthenoteapplicationwithAngular2.Whentheapplicationstarts,itwilltrytodownloadthelistofnotes.Iftheuserisnotloggedin,wewillgetanUnauthorizederror(statuscode401)andshowtheloginform.Otherwise,wecanshowthemenuwithallnotes,alogoutbutton,andabuttontocreateanewnote.Whenclickingonanote,thatnoteisdownloadedandtheusercanedititinthenoteeditor.Iftheuserclicksonthenewbutton,theusercanwritethenewnoteinthe(same)noteeditor.

Theserverusesacookietomanagethesession,sowedonothavetodothatmanuallyontheclientside.

WestartwithalmostthesameHTMLfilesavedasstatic/index.html:

<!DOCTYPEHTML>

<html>

<head>

<title>MyNotes</title>

<linkrel="stylesheet"href="style.css"/>

</head>

<body>

<divid="wrapper">

<note-application>Loading..</note-application>

</div>

<scripttype="text/javascript">

varglobal=window;

</script>

<scriptsrc="scripts/scripts.js"type="text/javascript"></script>

</body>

</html>

Instatic/style.css,weaddsomestylesasfollows:

body{

font-family:'SegoeUI',Tahoma,Geneva,Verdana,sans-serif;

font-weight:100;

}

h1,h2,h3{

margin:00;

padding:00;

color:#C93524;

}

h2{

margin:00;

padding:00;

color:#1C5C91;

}

#wrapper{

position:absolute;

left:0;

right:0;

top:0;

Page 827: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

width:450px;

margin:10%auto;

}

a:link,a:visited{

color:#1C5C91;

text-decoration:underline;

}

a:hover,a:active{

color:#3B6282;

}

li>a:link,li>a:visited{

color:#C93524;

text-decoration:underline;

}

li>a:hover,li>a:active{

color:#AD4236;

}

label{

display:block;

}

Inlib/client/api.ts,wecreateafunction,getUrl,thatwillsimplifyAPIaccess.Withthisfunction,wecanwritegetUrl("login",{username:"lorem",password:"ipsum"})insteadof"login?username=lorem&password=ipsum".Thefunctionalsotakestheescapingofcharacters,suchasanampersand,intoaccount:

exportconstbaseUrl="/api/";

exportfunctiongetUrl(method:string,query:{[key:string]:string}){

leturl=baseUrl+method;

letseperator="?";

for(constkeyofObject.keys(query)){

url+=seperator+encodeURIComponent(key)+"="+

encodeURIComponent(query[key]);

seperator="&";

}

returnurl;

}

Page 828: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtheloginformNowwecancreatetheloginform,asshowninthefollowingscreenshot:

Inlib/client/login.ts,wecreatetheloginform.Westartwiththeimportsandthetemplate:

import{Component,Output,EventEmitter}from"angular2/core";

import{Http,HTTP_PROVIDERS}from"angular2/http";

import{getUrl}from"./api";

import{LoginResult}from"../shared/api";

@Component({

selector:"login-form",

template:`

<h2>Login</h2>

<form(submit)="submit($event)">

<div>{{message}}</div>

<label>Username<br/><input[(ngModel)]="username"/></label>

<label>Password<br/><inputtype="password"

[(ngModel)]="password"/></label>

<buttontype="submit">Login</button>

</form>

`,

viewProviders:[HTTP_PROVIDERS]

})

exportclassLoginForm{

username:string;

password:string;

message:string;

constructor(privatehttp:Http){}

Herewewillsubmittheusernameandpasswordtotheserver.Iftheloginissuccessful,weemitthesuccessevent.Themaincomponentcanthenhidetheloginpageandshowthemenu:

submit(e:Event){

e.preventDefault();

this.http.get(getUrl("login",{username:this.username,password:

this.password}))

.map(response=>response.json())

.subscribe((response:LoginResult)=>{

if(response.ok){

this.success.emit(undefined);

}else{

this.message=response.message;

}

});

Page 829: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

@Output()

success=newEventEmitter();

}

Page 830: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingamenuInlib/client/menu.ts,wecreatethemenu.Inthemenu,theuserwillseehis/hernotesandcancreateanewnote.Themenuwilllooklikethefollowing:

Thiscomponentcanemittwodifferentevents:createandopen.Thesecondhasanargument,sowehavetoaddstringastypeargument:

import{Component,Input,Output,EventEmitter}from"angular2/core";

import{MenuItem}from"../shared/api";

@Component({

selector:"notes-menu",

template:`

<buttontype="button"(click)="clickCreate()">New</button>

<ul>

<li*ngFor="#itemofitems">

<ahref="javascript:;"(click)="clickItem(item)">{{

item.title}}</a>

</li>

</ul>

`

})

exportclassMenu{

@Input()

items:MenuItem[];

@Output()

create=newEventEmitter();

@Output()

open=newEventEmitter<string>();

clickCreate(){

this.create.emit(undefined);

}

clickItem(item:MenuItem){

this.open.emit(item.id);

}

}

Page 831: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThenoteeditorThenoteeditorisasimpletextarea.Aboveit,wewillshowthetitleofthenote.Withtwo-waybindings,thetitleisautomaticallyupdatedwhenthecontentofthetextareaischanged.

Page 832: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThemaincomponentNowwecanwritethemaincomponent.Thiscomponentwillshowoneoftheothercomponents,dependingonthestate.Firstwemustimportrxjs,Angular,andthefunctionsandcomponentswehavealreadywritten:

import"rxjs";

import{Component}from"angular2/core";

import{bootstrap}from"angular2/platform/browser";

import{Http,HTTP_PROVIDERS,Response}from"angular2/http";

import{getUrl}from"./api";

import{MenuItem,MenuResult,ItemResult}from"../shared/api";

import{LoginForm}from"./login";

import{Menu}from"./menu";

import{NoteEditor}from"./note";

Wewilluseanenumtypetostorethestate:

enumState{

Login,

Menu,

Note,

Error

}

Thetemplateshowstherightcomponentbasedonthestate.Thesecomponentshavesomeeventlistenersattached:

@Component({

selector:"note-application",

viewProviders:[HTTP_PROVIDERS],

directives:[LoginForm,Menu,NoteEditor],

template:`

<h1>MyNotes</h1>

<login-form*ngIf="stateLogin"(success)="loadMenu()"></login-form>

<div*ngIf="!stateLogin">

<ahref="javascript:;"(click)="logout()">Logout</a>

</div>

<notes-menu*ngIf="stateMenu"[items]="menu"(create)="createNote()"

(open)="loadNote($event)"></notes-menu>

<note-editor*ngIf="stateNote&&note"[content]="note.content"

(save)="save($event)"(remove)="remove($event)"></note-editor>

<div*ngIf="stateError">

<h2>Somethingwentwrong</h2>

Reloadthepageandtryagain

</div>

`

})

Inthebodyoftheclass,wehavetoaddsomepropertiesforthestatefirst:

classApplication{

state=State.Menu;

Page 833: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

constructor(privatehttp:Http){

this.loadMenu();

}

getstateLogin(){

returnthis.state===State.Login;

}

getstateMenu(){

returnthis.state===State.Menu;

}

getstateNote(){

returnthis.state===State.Note;

}

getstateError(){

returnthis.state===State.Error;

}

menu:MenuItem[]=[];

note:ItemResult=undefined;

Errorhandler

Nowwewillwriteafunctionthatwillloadthemenu.ErrorswillbepassedtohandleError.Iftheuserwasnotauthenticated,wewillfindthestatuscode401hereandshowtheloginform.Forasuccessfulrequest,wecancasttheresponsetotheinterfaceswedefinedinlib/shared/api.ts:

handleError(error:Response){

if(error.status===401){

//Unauthorized

this.state=State.Login;

this.menu=[];

this.note=undefined;

}else{

this.state=State.Error;

}

}

loadMenu(){

this.state=State.Menu;

this.menu=[];

this.http.get(getUrl("note/list",{})).subscribe(response=>{

constbody=<MenuResult>response.json();

this.menu=body.items;

},error=>this.handleError(error));

}

Weimplementtheeventlisteners,createNoteandloadNote,ofthemenu:

createNote(){

this.note={

id:undefined,

content:""

};

this.state=State.Note;

}

Page 834: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

loadNote(id:string){

this.note=undefined;

this.http.get(getUrl("note/find",{id:id})).subscribe(response=>

{

this.state=State.Note;

this.note=<ItemResult>response.json();

},error=>this.handleError(error));

}

Insave,wehavetocheckwhetherthenoteisneworbeingupdated:

save(content:string){

leturl:string;

this.note.content=content;

if(this.note.id===undefined){

//Newnote

url=getUrl("note/insert",{content:this.note.content});

}else{

//Existingnote

url=getUrl("note/update",{id:this.note.id,content:

this.note.content});

}

this.state=State.Note;

this.note=undefined;

this.http.get(url).subscribe(response=>{

this.loadMenu();

},error=>this.handleError(error));

}

remove(){

if(this.note.id===undefined){

this.loadMenu();

return;

}

this.http.get(getUrl("note/remove",{id:this.note.id

})).subscribe(response=>{

this.loadMenu();

},error=>this.handleError(error));

}

logout(){

this.http.get(getUrl("logout",{})).subscribe(response=>{

this.state=State.Login;

this.menu=[];

this.note=undefined;

},error=>this.handleError(error));

}

}

bootstrap(Application).catch(err=>console.error(err));

Page 835: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RunningtheapplicationTotesttheapplication,theserverandthestaticfileshavetobeservedfromthesameserver.Todothat,youcanusethehttp-serverpackage.Thatservercanservethestaticfilesandpassthrough(proxy)therequeststotheAPIserver.IfMongoDBisnotrunningyet,openaterminalwindowandrunmongod--dbpath./data.OpenaterminalwindowintherootoftheprojectandrunthefollowingtostarttheAPIserveronlocalhost:8800:

gulp&&node--harmony_destructuringdist/server

Inanewterminalwindow,navigatetothestaticdirectory.Installhttp-serverusingthefollowingcommand:

npminstallhttp-server-g

Nowyoucanstarttheserver:

http-server-Phttp://localhost:8800

Openlocalhost:8080inabrowserandyouwillseetheapplicationthatwehavecreated.

Page 836: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,youcreatedaclient-serverapplication.YouusedNodeJStocreateaserver,withMongoDBandPhaethon.Youalsolearnedmoreaboutasynchronousprogrammingandthestructuraltypesystem.WeusedourknowledgeofAngularfromthefirstchaptertocreatetheclientside.

Inthenextchapter,wewillcreateanotherclient-serverapplication.ThatapplicationisnotaCRUDapplication,butareal-timechatapplication.WewillbeusingReactinsteadofAngular.

Page 837: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter4.Real-TimeChatAfterhavingwrittentwoapplicationswithAngular2,wewillnowcreateonewithReact.Theserverpartwillalsobedifferent.Insteadofaconnectionlessserver,wewillnowcreateaserverwithapersistentconnection.Inthepreviouschapters,theclientsentrequeststotheserverandtheserverrespondedtothem.Nowwewillwriteaserverthatcansendinformationatanytimetotheclient.Thisisneededtosendnewchatmessagesimmediatelytotheclient,asshowninthefollowing:

Inthechatapplication,ausercanfirstchooseausernameandjoinachatroom.Intheroom,he/shecansendmessagesandreceivemessagesfromotherusers.Inthischapter,wewillcoverthefollowingtopics:

SettinguptheprojectGettingstartedwithReactWritingtheserverConnectingtotheserverCreatingthechatroomComparingReactandAngular

Page 838: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SettinguptheprojectBeforewecanstartcoding,wehavetosetuptheproject.ThedirectorystructurewillbethesameasinChapter3,Note-TakingAppwithServer;staticcontainsthestaticfilesforthewebserver,lib/clientcontainstheclient-sidecode,lib/servercontainsthecodefortheserver,lib/sharedcontainsthecodethatcanbeusedonbothsides,andlib/typingscontainsthetypedefinitionsforReact.

Wecaninstallalldependencies,forgulp,theserver,andReact,asfollows:

npminit

npminstallreactreact-domws--save

npminstallgulpgulp-sourcemapsgulp-typescriptgulp-uglifysmall--save-dev

Thetypedefinitionscanbeinstalledusingnpm:

cdlib

npminstall@types/node@types/react@types/react-dom@types/ws--save

Wecreatestatic/index.html,whichwillloadthecompiledJavaScriptfile:

<!DOCTYPEHTML>

<html>

<head>

<title>Chat</title>

<linkhref="style.css"rel="stylesheet"/>

</head>

<body>

<divid="app"></div>

<scripttype="text/javascript">

varprocess={

env:{

NODE_ENV:"DEBUG"//or"PRODUCTION"

}

};

</script>

<scriptsrc="scripts/scripts.js"type="text/javascript"></script>

</body>

</html>

Weaddstylesinstatic/style.css:

body{

font-family:'SegoeUI',Tahoma,Geneva,Verdana,sans-serif;

}

label,input,button{

display:block;

}

Page 839: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringgulpWecanusealmostthesamegulpfile.WedonothavetoloadanypolyfillsforReact,sotheresultingfileisevensimpler:

vargulp=require("gulp");

varsourcemaps=require("gulp-sourcemaps");

vartypescript=require("gulp-typescript");

varsmall=require("small").gulp;

varuglify=require("gulp-uglify");

vartsServer=typescript.createProject("lib/tsconfig.json",{

typescript:require("typescript")});

vartsClient=typescript.createProject("lib/tsconfig.json",{typescript:

require("typescript"),target:"es5"});

gulp.task("compile-client",function(){

returngulp.src(["lib/client/**/*.ts","lib/client/**/*.tsx",

"lib/shared/**/*.ts"],{base:"lib"})

.pipe(sourcemaps.init())

.pipe(typescript(tsClient))

.pipe(small("client/index.js",{outputFileName:{

standalone:"scripts.js"},externalResolve:

["node_modules"]}))

.pipe(sourcemaps.write("."))

.pipe(gulp.dest("static/scripts"));

});

gulp.task("compile-server",function(){

returngulp.src(["lib/server/**/*.ts","lib/shared/**/*.ts"],{

base:"lib"})

.pipe(sourcemaps.init())

.pipe(typescript(tsServer))

.pipe(sourcemaps.write("."))

.pipe(gulp.dest("dist"));

});

gulp.task("release",["compile-client","compile-server"],function(){

returngulp.src("static/scripts/**.js")

.pipe(uglify())

.pipe(gulp.dest("static/scripts"));

});

gulp.task("default",["compile-client","compile-server"]);

Inlib/tsconfig.json,weconfigureTypeScript.Wehavetosetthejsxoption.InReact,viewsarewritteninanXML-likelanguage,JSX,insideJavaScript.TousethisinTypeScript,youhavetosetthejsxoptionandusethefileextension.tsxinsteadof.jsx.

{

"compilerOptions":{

"module":"commonjs",

"target":"es6",

"jsx":"react",

"types":["node"]

Page 840: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

}

Page 841: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GettingstartedwithReactJustlikeAngular,Reactiscomponentbased.Angulariscalledaframework,whereasReactiscalledalibrary.ThismeansthatAngularprovidesalotofdifferentfunctionalitiesandReactprovidesonefunctionality,views.Inthefirsttwochapters,weusedtheHTTPserviceofAngular.Reactdoesnotprovidesuchaservice,butyoucanuseotherlibrariesfromnpminstead.

Page 842: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingacomponentwithJSXAcomponentisaclassthathasarendermethod.ThatmethodwillrendertheviewandisthereplacementofthetemplateinAngular.Asimplecomponentwouldlooklikethefollowing:

exportclassExampleextendsReact.Component<{},{}>{

render(){

constname="World";

return(

<div>

Hello,{name}!

<buttononClick={()=>alert("Hello")}>

Clickme

</button>

</div>

);

}

}

Asyoucansee,youcanembedHTMLinsidetherenderfunction.Expressionscanbewrappedinsidecurlybrackets,bothintextandinpropertiesofothercomponents.Eventhandlerscanbeaddedinthiswaytoo.Insteadofusingbuilt-incomponents,youcanusecustomcomponentsinthesehandlers.Allbuilt-incomponentsstartwithalowercasecharacterandcustomelementsshouldstartwithanuppercasecharacter.Thisisnotjustaconvention,butrequiredbyReact.Wehavetouseadifferentsyntaxfortypecastsin.tsxfiles,asthenormalsyntaxconflictswiththeXMLelements.Insteadof<Type>value,wewillnowwritevalueasType.In.tsfiles,wecanusebothstyles.

Page 843: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingpropsandstatetoacomponentIntheexample,thecomponentextendstheReact.Componentclass.Thatclasshastwotypearguments,whichrepresentthepropsandthestate.Thepropscontaintheinputthattheparentcomponentgivestothisone.Youcancomparethattothe@InputdirectiveinAngular.Youcannotmodifythepropsinthecontainingclass.ThestatecontainstheotherpropertiesofacomponentinAngular,whichcanbemodifiedintheclass.Youcanaccessthepropswiththis.propsandthestatewiththis.state.Thestatecannotbemodifieddirectly,asyouhavetoreplacethestatewithanewobject.Imaginethestatecontainstwoproperties,fooandbar.Ifyouwanttomodifyfooandbar,itisnotallowedwiththis.state.foo=42,butyouhavetowritethis.setState({foo:42,bar:true})instead.Inmostcases,youdonothavetochangeallpropertiesofthestate.Insuchcases,youonlyhavetospecifythepropertiesthatyouwanttochange.Forinstance,this.setState({foo:42,bar:true})willchangethevalueoffooandkeeptheoldvalueofbar.Thestateobjectisthenreplacedbyanewobject.Thestateobjectwillneverchange.Suchanobjectiscalledanimmutableobject.WewillreadmoreontheseobjectsinChapter5,NativeQRScannerApp.

Thecomponentwillbere-renderedbyReactaftercallingsetState.

Inotherpartsoftheapplication,wewillalsoneedtomodifyafewpropertiesofanobject.Forbigobjects,thisbecomesannoying.Wecreateahelperfunction,whichrequirestheoldstate,addsmodificationstoit,andreturnsanewstate.Thisfunctiondoesnotchangetheoldstate,butreturnsanewone.Inlib/client/model.ts,wecreatethemodifyfunction:

exportfunctionmodify<UextendsV,V>(old:U,changes:V){

constresult:any=Object.create(Object.getPrototypeOf(old));

for(constkeyofObject.keys(old)){

result[key]=old[key];

}

for(constkeyofObject.keys(changes)){

result[key]=changes[key];

}

return<U>result;

}

Page 844: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthemenuWewillstartwiththemenuofourapplication.Inthemenu,theusercanchoosethechatroomthathe/shewantstojoin.Themenuwillfirstasktheuserforausername.Afterward,theusercantypethenameofachatroom.Theuserwillgetcompletionsforknownrooms,buthe/shecanalsocreateanewroom.Let'scheckthefollowingscreenshotasanexampleofmenu:

Thecomponentwilldelegatethecompletionstoitsparent,soweneedtoaddthecurrentlistofcompletionstotheprops,suchthattheparentcansetit.Also,weneedtoaddacallbackthatcanbecalledwhenthecompletionsmustbefetched.

Thestatemustcontaintheusernameandtheroomname.Reactdoesnothavetwo-waybindings,sowehavetouseeventlistenerstoupdatetheusernameandroomnameinthestate.

Wewilldisabletherestofthemenuiftheuserhasn'tprovidedtheusername.Whentheuserhasfilledinaroom,weshowalistofcompletionsandabuttontocreateanewroomwiththespecifiedname.

Wewritethecodeinlib/client/menu.tsx.First,wedefinethepropsandstateintwodifferentinterfaces:

import*asReactfrom"react";

import{modify}from"./model";

interfaceMenuProps{

completions:string[];

onRequestCompletions:(room:string)=>void;

onClick:(username:string,room:string)=>void;

}

interfaceMenuState{

username:string;

roomname:string;

}

Second,wecreatetheclass.Wesettheinitialstatewithanemptyusernameandroomname:

exportclassMenuextendsReact.Component<MenuProps,

MenuState>{

state={

username:"",

roomname:""

};

Page 845: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Intherenderfunction,weuseJSXtoshowthecomponent.WecanusenormalTypeScriptconstructs.ThereisnoneedtousesomethinglikeNgIforNgFor,aswedidinAngular:

render(){

constmenuEnabled=this.state.username!=="";

constmenuStyle={

opactity:menuEnabled?1:0.5

};

constshowCreateButton=menuEnabled

&&this.state.roomname!==""

&&this.props.completions

.indexOf(this.state.roomname)===-1;

return(<div>

<labelhtmlFor="username">Username</label>

<inputtype="text"id="username"onChange=

{e=>this.changeUsername(

(e.targetasHTMLInputElement).value)}/>

<divstyle={menuStyle}>

<labelhtmlFor="roomname">Room</label>

<inputtype="text"id="roomname"

disabled={!menuEnabled}

onChange={e=>

this.changeName(

(e.targetasHTMLInputElement).value)

}/>

{showCreateButton

?<buttononClick={

()=>this.submit(this.state.roomname)}>

Createroom{this.state.roomname}</button>

:""}

{this.props.completions.map(

completion=>

<ahref="javascript:;"

key={completion}

style={{display:"block"}}

onClick={()=>

this.submit(completion)}>

{completion}</a>)}

</div>

</div>);

}

Finally,wecanimplementthelisteners:

privatechangeUsername(username:string){

this.setState({username});

}

privatechangeName(roomname:string){

this.setState({roomname});

this.props.onRequestCompletions(roomname);

}

privatesubmit(room:string){

this.props.onClick(this.state.username,room);

}

}

Page 846: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wecanseethiscomponentinactionbyaddingthefollowinginlib/client/index.tsx:

ReactDOM.render(<Menucompletions={[]}onRequestCompletions={()=>{}}onClick=

{()=>{}}/>);

ThiswillrenderthemenuintheHTMLfile.

Page 847: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheapplicationToviewtheapplicationinabrowser,youmustfirstbuilditusinggulp.Youcanexecutegulpinaterminal.Afterward,youcanopenstatic/index.htmlinabrowser.

Page 848: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WritingtheserverToaddinteractiontotheapplication,wemustcreatetheserverfirst.Wewillusethewspackagetoeasilycreateawebsocketserver.Onthewebsocket,wecansendmessagesinbothdirections.ThesemessagesareobjectsconvertedtostringswithJSON,justlikeinthepreviouschapters.

Page 849: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConnectionsInthepreviouschapter,wewroteaconnectionlessserver.Foreveryrequest,anewconnectionwassetup.Wecouldstoreastateusingasession.Suchsessionwasidentifiedwithacookie.Ifyouweretocopythatcookietoadifferentcomputer,youwouldhavethesamesessionthere.

Nowwewillwriteaserverthatusesconnections.Inthisway,theservercaneasilykeeptrackofwhichuserisloggedinandwhere.Theservercanalsosendamessagetotheclientwithoutadirectrequest.Thisautomaticupdatingiscalledpushing.Theopposite,pulling,orpolling,meansthattheclientconstantlyaskstheserverwhetherthereisnewdata.

Withconnections,theorderofarrivalisthesameastheorderofsending.Withaconnectionlessserver,asecondmessagecanuseadifferentrouteandarriveearlier.

Page 850: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypingtheAPIWewilltypethesemessagesinlib/shared/api.ts.Inthepreviouschapter,theURLidentifiedthefunctiontobecalled.Now,wemustincludethatinformationinthemessageobject.Wetypethemessagesfromtheclienttotheserverandviceversa:

exportenumMessageKind{

FindRooms,

OpenRoom,

SendMessage,

RoomCompletions,

ReceiveMessage,

RoomContent

}

exportinterfaceMessage{

kind:MessageKind;

}

exporttypeClientMessage=OpenRoom|ChatMessage|FindRooms;

exporttypeServerMessage=RoomContent|ChatMessage;

exportinterfaceFindRoomsextendsMessage{

query:string;

}

exportinterfaceOpenRoomextendsMessage{

room:string;

}

exportinterfaceRoomCompletionsextendsMessage{

completions:string[];

}

exportinterfaceRoomContentextendsMessage{

room:string;

messages:ChatContent[];

}

exportinterfaceSendMessageextendsMessage{

text:string;

}

exportinterfaceChatMessageextendsMessage{

content:ChatContent

}

exportinterfaceChatContent{

room:string;

username:string;

content:string;

}

Page 851: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AcceptingconnectionsInlib/server/index.ts,wecreateaserverthatlistensfornewconnections.Wealsokeeptrackofallopenconnections.Whenamessageissentinachatroom,itcanbeforwardedtoallsessionsthathaveopenedthatroom.Weusewstocreateawebsocketserver:

import*asWebSocketfrom"ws";

import*asapifrom"../shared/api";

constserver=newWebSocket.Server({port:8800});

server.on("connection",receiveConnection);

interfaceSession{

sendChatMessage(message:api.ChatContent):void;

}

constsessions:Session[]=[];

Wewillstoretherecentmessagesinanarray.Welimitthesizeofthearray,asanattackercouldotherwisefillthewholememoryofaserverwitha(D)DOSattack:ifausersendsalotofmessages(automatically),thiswillcostalotofservermemory.Ifmultipleusersdothat,thememorycanbefilledentirelyandtheserverwillcrash.

Page 852: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StoringrecentmessagesYoucanimplementthiswithanarraybyremovingthefirstmessageandappendingthenewmessageattheend.However,thiswouldshiftthewholearray,especiallylargearraysthatcantakesometime.Instead,weuseadifferentapproach.Weuseanarraythatcanbeseenasacircle:afterthelastelementcomesthefirstone.Weuseavariablethatpointstotheoldestmessage.Whenanewmessageisadded,theitematthepositionofthepointerisoverwrittenwiththenewmessage.Thepointerisincrementedwithoneandpointsagaintotheoldestmessage.WhenthemessagesA,B,CandDaresentwithanarraysizeof3,thiscanbevisualizedlikethefollowing:

[-,-,-];pointer=0

[A,-,-];pointer=1

[A,B,-];pointer=2

[A,B,C];pointer=0

[D,B,C];pointer=1

IfyouarefamiliarwithanalyzingalgorithmsandBig-Ohnotation,thistakesO(1),whereasthenaiveideatakesO(n).Wecreatethearrayinlib/server/index.ts:

constrecentMessages:api.ChatContent[]=newArray(2048);

letrecentMessagesPointer=0;

Wedonotsavethemessagestodisk.Youcoulddothatanduseacachewithsucharraytoincreasetheperformanceoftheserver.

Page 853: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

HandlingasessionForeachconnection,wehavetokeeptrackoftheusernameandroomnameoftheuser.WecandothatwithvariablesinsidethereceiveConnectionfunction:

functionreceiveConnection(ws:WebSocket){

letusername:string;

letroom:string;

Wecanlistentothemessageandcloseevents.Thefirstisemittedwhentheclienthassentamessageinthewebsocket.Thesecondisemittedwhenthewebsockethasbeenclosed.Whenthesocketisclosed,wemustnotsendanymessagestoitandwemustremoveitfromthesessionsarray:

ws.on("message",message);

ws.on("close",close);

constsession:Session={sendChatMessage};

sessions.push(session);

functionmessage(data){

try{

constobject=<api.ClientMessage>JSON.parse(data);

if(typeofobject.kind!=="number")return;

switch(object.kind){

caseapi.MessageKind.FindRooms:

findRooms(<api.FindRooms>object);

caseapi.MessageKind.OpenRoom:

openRoom(<api.OpenRoom>object);

break;

caseapi.MessageKind.SendMessage:

chatMessage(<api.SendMessage>object);

break;

}

}catch(e){

console.error(e);

}

}

functionclose(){

constindex=sessions.indexOf(session);

sessions.splice(index,1);

}

functionsend(data:api.ServerMessage){

ws.send(JSON.stringify(data));

}

Theservershouldalwaysvalidatetheinputthatitgets.ThedatacouldnotbeaJSONstring,whichwouldcauseJSON.parsetothrowanerror.object.kindmightnotbeanumber,asTypeScriptdoesnotdoanyruntimechecks.Wecanvalidatethatwithatypeofcheck.

Tip

Ifyouwouldnothaveaddedatry/catch,theserverwouldcrashiftheclientsendsamessage

Page 854: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thatisnotthecorrectJSON.Topreventthis,wewillcatchthaterror.Fordebugging,wewritetheerrorontheconsole.

Page 855: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingachatmessagesessionNowwecanimplementthefunctionsthatarecalledwhenamessagecomesin.Westartwiththefunctionthatsendsachatmessagetoallactiveconnectionsinthatroomandstoresitinthearraywithrecentmessages:

functionsendChatMessage(content:api.ChatContent){

if(content.room===room){

send({

kind:api.MessageKind.ReceiveMessage,

content

});

}

}

functionchatMessage(message:api.SendMessage){

if(typeofmessage.content!=="string")return;

constcontent:api.ChatContent={

room,

username,

content:message.content

};

recentMessages[recentMessagesPointer]=content;

recentMessagesPointer++;

if(recentMessagesPointer>=recentMessages.length){

recentMessagesPointer=0;

}

for(constitemofsessions){

if(session!==item)item.sendChatMessage(content);

}

}

Thiswillsendachatmessagetoallothersessionsinthesameroom.WeinsertthemessageattherightlocationinrecentMessagesandadjustthepointer.

Finally,wewillwritethefunctionthatgivescompletionsforroomnames.Wedonothaveanarrayofroomnames,sowehavetogetthatinformationfromtherecentmessages.Theresultingarraycancontainduplicates,sowehavetoremovethese.Anaiveapproachwouldbetocheckforeveryelementifithasoccurredbeforeinthearray.However,thisisaslowoperation.Instead,wesortthearrayfirst.Aftersorting,weonlyhavetocompareeachelementwiththeelementbeforeit.Iftheseareequal,thesecondisaduplicate,otherwiseitisnot.ForthosefamiliarwithBig-Oh,thefirstapproachcostsO(n^2)andthesecondonecostsO(nlog(n)).Thisresultsinthefollowingfunction:

functionfindRooms(message:api.FindRooms){

constquery=message.query;

if(typeofquery!=="string")return;

constrooms=recentMessages

Page 856: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

.map(msg=>msg.room)

.filter(room=>room.toLowerCase().indexOf(query.toLowerCase())!==-1)

.sort();

constcompletions:string[]=[];

letprevious:string=undefined;

for(letroomofrooms){

if(previous!==room){

completions.push(room);

previous=room;

}

}

send({

kind:api.MessageKind.RoomCompletions,

completions

});

}

}

Wehavecompletedtheserverandcanfocusontheclientsideagain.

Page 857: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConnectingtotheserverWecanconnecttotheserverwiththeWebSocketclass:

constsocket=newWebSocket("ws://localhost:8800/");

Sincewe'reusingReact,weaddthefollowingtothestate.Wecreateanewcomponent,App,thatwillshowthemenuorachatroombasedonthestate.Inlib/client/index.tsx,wefirstdefinethestateandpropsofthatcomponent:

import*asReactfrom"react";

import*asReactDOMfrom"react-dom";

import*asapifrom"../shared/api";

import*asmodelfrom"./model";

import{Menu}from"./menu";

import{Room}from"./room";

interfaceProps{

apiUrl:string;

}

interfaceState{

socket:WebSocket;

username:string;

connected:boolean;

completions:string[];

room:model.Room;

}

classAppextendsReact.Component<Props,State>{

state={

socket:undefined,

username:'',

connected:false,

completions:[],

room:undefined

};

Page 858: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AutomaticreconnectingNextup,wewillwriteafunction,connect,thatconnectstotheserverusingaWebSocket.WecallthatfunctionincomponentDidMount,whichiscalledbyReact.Wemustalsocallconnectwhentheconnectiongetsclosedforsomereason(forinstance,networkproblems).Westorethesocketinthestateandwealsokeeptrackofwhethertheclientisconnected:

connect(){

if(this.state.connected)return;

constsocket=newWebSocket(this.props.apiUrl);

this.setState({socket});

socket.onopen=()=>{

this.setState({connected:true});

if(this.state.room){

this.openRoom(this.state.username,this.state.room.name);

}

};

socket.onmessage=e=>this.onMessage(e);

socket.onclose=e=>{

this.setState({connected:false});

setTimeout(()=>this.connect(),400);

};

}

onMessage(e:MessageEvent){

constmessage=JSON.parse(e.data.toString())asapi.ServerMessage;

if(message.kind===api.MessageKind.RoomCompletions){

this.setState({

completions:(messageasapi.RoomCompletions).completions

});

}elseif(message.kind===api.MessageKind.RoomContent){

this.setState({

room:{

name:(messageasapi.RoomContent).room,

messages:(messageasapi.RoomContent).messages.map(msg=>this.mapMessage(msg))

}

});

}elseif(message.kind===api.MessageKind.ReceiveMessage){

this.addMessage(this.mapMessage((messageasapi.ReceiveMessage).content));

}

}

componentDidMount(){

this.connect();

}

socket.onmessageiscalledwhentheclientreceivesamessagefromtheserver.Basedonthekindofmessage,itissenttosomefunctionthatwewillimplementlater.First,wewillwritetherenderfunction.Afterwehavewrittentherenderfunction,weknowwhicheventhandlerswehavetowrite.

Tip

Whenyouwritetopdown,youfirstwritethemainfunctionandafterwardthehelperfunctionsthatthemainfunctionrequires.Withthebottomupapproach,youwritethehelper

Page 859: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

functionsbeforeyouwritethemainfunction.Inthissection,wewritethehelperfunctionslast,sowewritetopdown.Youcantrybothstylesandfindoutwhatyoulikemost.

Inrender,werenderthecomponentbasedonthestate—ifthereisnoconnection,weshowConnecting...,iftheuserisinaroom,weshowthatchatroom,otherwiseweshowthemenu:

render(){

if(!this.state.connected){

return<div>Connecting...</div>;

}

if(this.state.room){

return<Roomroom={this.state.room}onPost={content=>

this.post(content)}/>;

}

return<Menu

completions={this.state.completions}

onRequestCompletions={query=>

this.requestCompletions(query)}

onClick={(username,room)=>

this.openRoom(username,room)}

/>;

}

Page 860: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SendingamessagetotheserverBeforewritingtheeventhandlers,wefirstwriteasmallfunctionthatsendsamessagetotheserver.ItconvertsanobjecttoJSON,andTypeScriptwillcheckthatwearesendingacorrectmessagetotheserver:

privatesend(message:api.ClientMessage){

this.state.socket.send(JSON.stringify(message));

}

TherequestCompletionsandopenRoomfunctionssendamessagetotheserver.InopenRoom,wealsohavetostoretheusernameinthestate:

privaterequestCompletions(query:string){

this.send({

kind:api.MessageKind.FindRooms,

query

});

}

privateopenRoom(username,room:string){

this.send({

kind:api.MessageKind.OpenRoom,

username,

room

});

this.setState({username});

}

Page 861: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WritingtheeventhandlerForiterationsinReact,everyelementshouldhaveakeythatcanidentifyit.Thus,weneedtogiveeverymessagesuchakey.Weuseasimplenumerickey,whichwewillincrementforeverymessage:

privatenextMessageId:number=0;

privatepost(content:string){

this.send({

kind:api.MessageKind.SendMessage,

content

});

this.addMessage({

id:this.nextMessageId++,

user:this.state.username,

content,

isAuthor:true

});

}

privateaddMessage(msg:model.Message){

constmessages=[

...this.state.room.messages,

msg

].slice(Math.max(0,this.state.room.messages.length-10));

constroom=model.modify(this.state.room,{

messages

});

this.setState({room});

}

privatemapMessage(msg:api.ChatContent){

return{

id:this.nextMessageId++,

user:msg.username,

content:msg.content,

isAuthor:msg.username===this.state.username

};

}

}

Finally,wecanshowthecomponentintheHTMLfile:

ReactDOM.render(

<AppapiUrl="ws://localhost:8800/"/>,document.getElementById("app")

);

Wehavenowwrittenalleventhandlersandinteractionwiththeserver.Wewritethechatroomcomponentinthenextsection.

Page 862: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthechatroomWedividethechatroomintotwosubcomponents:amessageandtheinputbox.Whentheusersendsanewmessage,itissenttothemaincomponent.Messageoftheuserwillbeshownontherightandothermessagesontheleft,asshowninthefollowingscreenshot:

Page 863: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Two-waybindingsReactdoesnothavetwo-waybindings.Instead,wecanstorethevalueinthestateandmodifyitwhentheonChangeeventisfired.Fortheinputbox,wewillusethistechnique.Thetextboxshouldbeemptiedwhentheuserhassenthis/hermessage.Withthisbinding,wecaneasilydothatbymodifyingthevalueinthestatetoanemptystring:

classInputBoxextendsReact.Component<{onSubmit(value:string):void;},{

value:string}>{

state={

value:""

};

render(){

return(

<formonSubmit={e=>this.submit(e)}>

<inputonChange={e=>this.changeValue((e.targetas

HTMLInputElement).value)}value={this.state.value}/>

<buttondisabled={this.state.value===""}

type="submit">Submit</button>

</form>

);

}

privatechangeValue(value:string){

this.setState({value});

}

privatesubmit(e:React.FormEvent<{}>){

e.preventDefault();

if(this.state.value){

this.props.onSubmit(this.state.value);

this.state.value="";

}

}

}

Page 864: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StatelessfunctionalcomponentsIfacomponentdoesn'tneedastate,thenitdoesnotneedaclasstostoreandmanagethatstate.Insteadofwritingaclasswithjustarenderfunction,youcanwritethatfunctionwithouttheclass.Thesecomponentsarecalledstatelessfunctionalcomponents.Amessageisclearlystateless,asyoucannotmodifyamessagethathasalreadybeensent:

functionMessage(props:{message:model.Message}){

return(

<div>

<divclassName={props.message.isAuthor?"messagemessage-own":

"message"}>

{props.message.content}

<divclassName="message-user">

{props.message.user}

</div>

</div>

<divstyle={{clear:"both"}}></div>

</div>

);

}

Astatelessfunctionalcomponentcanhaveachildcomponentwithastate.TheinputboxhasastateandcanbeusedinsideRoom,whichisastatelesscomponent.Wehavetosetthekeypropertyinthearrayofmessages.Reactusesthistoidentifycomponentsinsidethearray:

exportfunctionRoom(props:{room:model.Room,onPost:(content:string)=>void

}){

return(

<div>

<h2>{props.room.name}</h2>

{props.room.messages.map(message=><Messagekey={message.id}

message={message}/>)}

<InputonSubmit={content=>props.onPost(content)}/>

</div>

);

}

Page 865: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RunningtheapplicationWecannowrunthewholeapplication.First,wemustcompileitwithgulp.Second,wecanstarttheserverbyrunningnodedist/serverinaterminal.Finally,wecanopenstatic/index.htmlinabrowserandstartchatting.Whenyouopenthispagemultipletimes,youcansimulatemultipleusers.

Page 866: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ComparingReactandAngularInthepreviouschapters,weusedAngularandinthischapterweusedReact.AngularandReactarebothfocusedoncomponents,buttherearedifferences,forinstance,betweenthetemplatesinAngularandtheviewsinReact.Inthissection,youcanreadmoreaboutthesedifferences.

Page 867: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TemplatesandJSXAngularusesatemplatefortheviewofacomponent.Suchatemplateisastringthatisparsedatruntime.TypeScriptcannotcheckthesetemplates.Ifyoumisspellapropertyname,youwillnotgetacompileerror.

ReactusesJSX,whichissyntacticsugararoundfunctioncalls.AJSXelementistransformed,bythecompiler,intoacalltoReact.createElement.Thefirstargumentisthenameoftheelementortheelementclass,thesecondargumentcontainstheprops,andtheotherargumentsarethechildrenofthecomponent.Thefollowingexampledemonstratesthetransform:

<div></div>;

React.createElement("div",null);

<divprop="a"></div>;

React.createElement("div",{prop:"a"});

<div>Foo</div>;

React.createElement(

"div",

null,

"Foo"

);

<div><span>Foo</span></div>;

React.createElement(

"div",

null,

React.createElement(

"span",

null,

"Foo"

)

);

Elementsthatstartwithacapitalletterorcontainadotareconsideredtobecustomcomponents,andotherelementsaretreatedasintrinsicelements,thestandardHTMLelements:

<div></div>;

React.createElement("div",null);

<Foo></Foo>;

React.createElement(Foo,null);

TheseJSXelementsarecheckedandtransformedatcompiletime,soyoudogetanearlyerrorwhenyoumisspellaproperty.ReactisnottheonlyframeworkthatusesJSX,butitisthemostpopularone.

Page 868: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

LibrariesorframeworksAngularisaframeworkandReactisalibrary.AlibraryprovidesonefunctionalityinthecaseofReact—theviewsoftheapplication.Aframeworkprovidesalotofdifferentfunctionalities.Forinstance,Angularrenderstheviewsoftheapplication,butalsohas,forinstance,dependencyinjectionandanHttpservice.IfyouwantsuchfeatureswhenyouareusingReact,youcanuseanotherlibrarythatgivesthatfeature.

ReactprogrammersoftenuseaFluxbasedarchitecture.Fluxisanapplicationarchitecturethatisimplementedinvariouslibraries.InChapter5,NativeQRScannerApp,wewilltakealookatthisarchitecture.

Page 869: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryWehavewrittenanapplicationwithwebsockets.WehaveusedReactandJSXfortheviewsofourapplication.WehaveseenmultiplewaystocreatecomponentsandlearnedhowtheJSXtransformworks.InChapter5,NativeQRScannerApp,wewilluseReactagain,butwewillfirsttakealookatmobileappswithNativeScriptinthenextchapter.

Page 870: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter5.NativeQRScannerAppWehavealreadyusedTypeScripttobuildwebappsandaserver.TypeScriptcanalsobeusedtocreatemobileapps.Inthischapter,wewillbuildsuchanapp.TheappcanscanQRcodes.Theappshowsalistofallpreviousscans.IfaQRcodecontainsaURL,theappcanopenthatURLinawebbrowser.VariousframeworksexistformakingmobileappsinTypeScript.WewilluseNativeScript,whichprovidesanativeuserinterfaceandrunsonAndroidandiOS,asshowninthefollowing:

Wewillcreatethisappwiththefollowingsteps:

CreatingtheprojectstructureCreatingaHelloWorldpageCreatingthemainviewAddingadetailsviewScanningQRcodesAddingpersistentstorageStylingtheappComparingNativeScripttoalternatives

Page 871: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GettingstartedwithNativeScriptInstallingNativeScriptrequiresseveralsteps.FordevelopingappsforAndroid,youhavetoinstallJavaDevelopmentKit(JDK)andtheAndroidSDK.AndroidappscanbebuiltonWindows,Linux,andMac.AppsforiOScanonlybebuiltonaMac.YouneedtoinstallXCodetobuildtheseapps.

YoucanfindmoredetailsonhowtoinstalltheAndroidSDKathttps://docs.nativescript.org/start/quick-setup.

AfterinstallingtheAndroidSDKorXCode,youcaninstallNativeScriptusingnpm:

npminstallnativescript-g

Youcanseewhetheryoursystemisconfiguredcorrectlybyrunningthefollowingcommand:

tnsdoctor

IfyouonlywanttodevelopappsforiOS,youcanignoretheerrorsonAndroidandviceversa.

WecantestmostpartsoftheappinasimulatorthatisincludedintheSDKorXCode.ScanningaQRcodeonlyworksonadevice.

Tip

SettingupXCodeforiOSdevelopmentiseasierthaninstallingtheAndroidSDK.IfyoucanchoosebetweeniOSandAndroid,youwanttochooseiOS.

Page 872: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtheprojectstructureInthepreviouschapters,wewroteourTypeScriptsourcesinthelibdirectory.Thestaticordistdirectorycontainedthecompiledsources.However,inthischapter,wehavetomakeadifferentstructuresinceNativeScripthassomerequirementsonit.NativeScriptrequiresthatthecompiledsourcesarelocatedintheappdirectoryanditusesthelibdirectoryforplugins,sowecannotusethatdirectoryforourTypeScriptsources.Instead,wewillusethesrcdirectory.

NativeScriptcanautomaticallycreateabasicprojectstructure.Byrunningthefollowingcommands,aminimalprojectwillbecreated:

tnsinit

npminstall

Thefirstcommandcreatesthepackage.jsonfileandtheappdirectory.NativeScriptstorestheiconsandsplashscreens(whichyouseewhentheappisloading)inapp.Youcaneditthesefileswhenyouwanttopublishanapp.ThenpminstallcommandinstallsthedependenciesthatNativeScriptneeds.Thesedependencieswereaddedtopackage.jsonbythefirstcommand.

Weneedtomakesomeadjustmentstoit.Wemustcreateanapp/package.jsonfile.NativeScriptusesthisfiletogetthemainfileoftheproject:

{

"main":"app.js"

}

Page 873: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingTypeScriptBydefault,NativeScriptappsshouldbewritteninJavaScript.WewillnotusegulptocompileourTypeScriptfilessinceNativeScripthasbuilt-insupportfortranspilerslikeTypeScript.WecanaddTypeScripttoitbyrunningthefollowingcommand:

tnsinstalltypescript

Afterrunningthiscommand,NativeScriptwillautomaticallycompileTypeScripttoJavaScript.Thiscommandhascreatedtwofiles:tsconfig.jsonandreferences.d.ts.ThetsconfigfilecontainstheconfigurationforTypeScript.WewilladdtheoutDiroptiontotsconfig.jsonsothatwedonothavetoplacethesourcefilesinthesamedirectionasthecompiledfiles.NativeScriptrequiresthatJavaScriptfilesareplacedintheappfolder.WewillwriteourTypeScriptsourcesinthesrcfolder,andthecompilerwillwritetheoutputtotheappfolder:

{

"compilerOptions":{

"module":"commonjs",

"target":"es5",

"inlineSourceMap":true,

"experimentalDecorators":true,

"noEmitHelpers":true,

"outDir":"app"

},

"exclude":[

"node_modules",

"platforms"

]

}

Thereferences.d.tsfilecontainsareferencetothedefinitionfiles(.d.tsfiles)ofthecoremodulesofNativeScript.

Page 874: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingaHelloWorldpageTogetstartedwithNativeScript,wewillfirstwriteasimpleapp.Insrc/app.ts,wemustregistermainEntrythatwillcreatetheviewoftheapp.TheentryshouldbeafunctionthatreturnsaPage.APageattributeisoneoftheclassesthatNativeScriptusesfortheuserinterface.Wecancreateabasicpageasfollows:

import*asapplicationfrom"application";

import{Page}from"ui/page";

import{Label}from"ui/label";

application.mainEntry=()=>{

constpage=newPage();

constlabel=newLabel();

label.text="Hello,World";

page.content=label;

returnpage;

};

application.start();

Thiswillcreateasinglelabelandaddittothepage.ThecontentofthepageshouldbeaViewclass,whichisthebaseclassthatallcomponents(includingLabel)inNativeScriptinherit.

YoucanruntheappwithoneofthefollowingcommandsforAndroidandiOS,respectively:

tnsrunandroid--emulator

tnsrunios--emulator

Youcanruntheapponadevicebyremoving--emulator.YourdeviceshouldbeconnectedusingaUSBcable.Youcanseeallconnecteddevicesbyrunningtnsdevice.

Tip

NativeScriptprintsalotontheconsoleaftertheoutputofTypeScript.MakesureyoudonotmissanycompileerrorsofTypeScript.

OniOS,theappwillnowlooklikethefollowing:

Inthenextsections,wewillseehowwecanaddeventlistenersandbuildbiggerviews.

Page 875: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthemainviewThemainviewwillshowalistofrecentscans.Clickingononeoftherecentscansopensadetailspagethatshowsmoredetailsonthescan.WhenauserclicksontheScanbutton,theusercanscanaQRcodeusingthecamera:

First,wecreatethemodelofascaninsrc/model.ts.Weneedtostorethecontent(astring)andthedateofthescan:

exportinterfaceScan{

content:string;

date:Date;

}

Insrc/view/main.ts,wewillcreatetheview.Theviewshouldexportafunctionthatcreatesthepage,sowecanuseitasthemainEntry.Italsoneedstoexportafunctionthatcanupdatethecontent.Theviewhastwocallbacksorevents:oneiscalledwhenanitemisclickedandtheotheriscalledwhentheuserclicksontheScanbutton.ThiscanbeimplementedbyaddingthetwocallbacksasargumentsofthecreatePagefunctionandreturningsetItems,whichupdatesthecontentofthelist,andcreateView,whichcreatesthePage,asanobject:

import{Page}from"ui/page";

import{ActionBar,ActionItem}from"ui/action-bar";

import{ListView}from"ui/list-view";

exportfunctioncreatePage(itemCallback:(index:number)=>void,scanCallback:

()=>void){

letitems:string[]=[];

letlist:ListView;

return{setItems,createView};

functionsetItems(value:string[]){

items=value;

if(list){

list.items=items;

list.refresh();

}

}

AnActionBaristhebaratthetopofthescreenwiththeappname.WeaddanActionItemattributetoit,whichisabuttoninthebar.WeuseaListViewattributetoshowtherecentscansinalist.Elementshaveanonmethod,whichweusetolistentoevents,similarto

Page 876: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

addEventListenerinwebsitesandoninNodeJS.

TheitemLoadingeventisfiredwhenaniteminthelistisbeingrendered.Inthatevent,theviewforanitemofthelistshouldbecreated.Thetapeventisfiredwhentheusertapsonthescanbutton.TheitemCallbackeventwillbeinvokedwiththeindexoftheitemwhenthathappens.

First,wecreatetheactionbar.Weaddittothepageandaddabuttontotheactionbar:

functioncreateView(){

constpage=newPage();

constactionBar=newActionBar();

actionBar.title="QRScanner";

constbuttonScan=newActionItem();

buttonScan.text="Scan";

buttonScan.on("tap",scanCallback);

actionBar.actionItems.addItem(buttonScan);

Next,wecreatethelistasfollows:

list=newListView();

list.items=items;

Finally,weaddeventlistenerstothelist.InitemLoading,wecreateLabel,ifitwasnotcreatedyet,andsetthetextofit.InitemTap,wecallitemCallbackwiththeindexofthetappeditem:

list.on("itemLoading",args=>{

if(!args.view){

args.view=newLabel();

}

(<Label>args.view).text=items[args.index];

});

list.on("itemTap",e=>itemCallback(e.index));

page.actionBar=actionBar;

page.content=list;

returnpage;

}

}

Insrc/app.ts,wecancallthisfunctionandshowtheviewattribute:

import*asapplicationfrom"application";

import{createPage}from"./view/main";

import*asmodelfrom"./model";

letitems:model.Scan[]=[];

constpage=createPage(index=>showDetailsPage(items[index]),scan);

application.mainEntry=page.createView;

application.cssFile="style.css";

application.start();

Page 877: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wewillimplementscanninglateron.Fornow,wewillalwaysaddafakescan,sowecantesttheotherpartsoftheapplication:

functionscan(){

addItem("Lorem");

}

InaddItem,weaddanewscantothelistofscans.Wecallupdate,whichwillupdatethelistinthemainviewandshowthedetailspagewiththisscan.Welimittheamountofscansinthelistby100:

functionaddItem(content:string){

constitem:model.Scan={

content,

date:newDate()

};

items=[item,...items].slice(0,100);

update();

showDetailsPage(item);

}

Wewillimplementthedetailspageinthenextsection.Fornow,wewillonlyaddaplaceholderfunctionsothatwecantesttheotherfunctions:

functionshowDetailsPage(scan:model.Scan){

}

Inupdate,wechangethevaluesinthelisttothenewitems:

functionupdate(){

page.setItems(items.map(item=>item.content));

}

Page 878: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingadetailsviewThedetailsviewisshownwhentheuserscansacodeorclicksonanitemintherecentscanslist.Itshowsthecontentofthescanandthedate,asshowninthefollowingscreenshot:

IfthecontentofthescanisaURL,wewillshowabuttontoopenthatlink,asshowninthefollowingscreenshot:

Attheendofthischapter,wewillstylethispageproperly.

Weaddafunctiontosrc/model.tsthatwillreturntruewhenthescan(probably)containsaURL.Weconsiderascanthatcontainsnospacesandbeginswithhttp://orhttps://tobeaURL:

functionstartsWith(input:string,start:string){

returninput.substring(0,start.length)===start;

}

exportfunctionisUrl({content}:Scan){

if(content.indexOf("")!==-1){

returnfalse;

}

returnstartsWith(content,"http://")||startsWith(content,"https://");

}

Theviewrequiresthescanitselfandoptionallyacallback.Thecallbackwillonlybeprovidedifthescancontainsalinkandthebuttonshouldbeshown.

NativeScripthasvariouswaystoshowmultipleelementsonapage.Apagecanonlycontainasinglecomponent,butNativeScripthascomponentsthatcancontainmultiplecomponents.Thesearecalledlayouts.Thesimplestone,andprobablyalsothemostused,istheStackLayout.Elementswillbeplacedbeloworbesideeachother.TheStackLayouthasapropertyorientationthatindicateswhethertheelementsshouldbeplacedbelow(vertical,default)orbeside(horizontal)eachother.

Otherlayoutsincludethefollowing:

Page 879: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DockLayout:Elementscanbeplacedontheleft,right,top,bottom,orcenterofthecomponent.GridLayout:Elementsareplacedinoneormultiplerowsandcolumnsinagrid.Thisisequaltoa<table>taginHTML.WrapLayout:Arowisfilledwithelements.Whenitisfull,thenextelementsareaddedtoanewrow.

Tip

Youcanfindallcomponentsathttp://docs.nativescript.org/ui/ui-viewsandalllayoutcontainersathttp://docs.nativescript.org/ui/layout-containers.

Insrc/view/details.ts,wewillimplementthispage:

import{EventData}from"data/observable";

import{topmost}from"ui/frame";

import{Page}from"ui/page";

import{ActionBar,ActionItem}from"ui/action-bar";

import{Button}from"ui/button";

import{Label}from"ui/label";

import{StackLayout}from"ui/layouts/stack-layout";

import*asmodelfrom"../model";

exportfunctioncreateDetailsPage(scan:model.Scan,callback?:()=>void){

return{createView};

functioncreateView(){

constpage=newPage();

constlayout=newStackLayout();

page.content=layout;

Inalabel,wewillshowthecontentofthescan.Wecanaddaclassnametoit,justlikeyouwoulddoonanHTMLwebpage.Lateron,wecanstylethispageusingCSS:

constlabel=newLabel();

label.text=scan.content;

label.className="details-content";

layout.addChild(label);

Thedateofthescanwillbeshowninasecondlabel:

constdate=newLabel();

date.text=scan.date.toLocaleString("en");

layout.addChild(date);

Ifacallbackisprovided,weshowabuttonthatwillopenthelinkofthescan:

if(callback){

constbutton=newButton();

button.text="Open";

button.on("tap",callback);

layout.addChild(button);

}

Page 880: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnpage;

}

}

Insrc/app.ts,wecannowimplementtheshowDetailsPagefunction.Usingtopmost().navigate,wecannavigatetothepage.UserscangobacktothemainpagewiththestandardbackbuttonofAndroidoriOS,whichisautomaticallyshown:

import{topmost}from"ui/frame";

import{openUrl}from"utils/utils";

import{createDetailsPage}from"./view/details";

...

functionshowDetailsPage(scan:model.Scan){

letcallback:()=>void;

if(model.isUrl(scan)){

callback=()=>openUrl(scan.content);

}

topmost().navigate(createDetailsPage(scan,callback).createView);

}

TheopenUrlfunctionopensawebbrowserwiththespecifiedURL.

Page 881: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ScanningQRcodesNativeScripthassupportforplugins.Aplugincanaddextrafunctionality,suchasturningontheflashlightofaphone,vibratingthephone,logginginwithFacebook,orscanningQRcodes.ThesecanbeinstalledusingthecommandlineinterfaceofNativeScript.

WewilluseaNativeScriptplugintoscanQRcodes.ThepluginiscalledNativeScriptBarcodeScanner.ItcanscanQRcodesandotherbarcodeformats.Theplugincanbeinstalledusingthefollowingcommand:

tnspluginaddnativescript-barcodescanner

Page 882: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypedefinitionsWemustaddadefinitionfiletoimporttheplugin.Theplugindoesnotcontaintypedefinitions,andtypedefinitionsarenotavailableonDefinitelyTypedandTSD.Itisnotnecessarytowritedefinitionsthatarefullycorrect.Weonlyhavetotypethepartsofthelibrarythatweareusing.Weusethescanfunction,whichcantakeanoptionalsettingsobjectandreturnaPromise.Insrc/definitions.d.ts,wewritethefollowingdefinition:

declaremodule"nativescript-barcodescanner"{

functionscan(options?:any):Promise<any>;

}

Tip

Youdonotneedtospecifytheexportkeywordindefinitionfiles.Alldeclarationsinamoduleinadefinitionfileareautomaticallyconsideredtobeexported.

Page 883: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementationThescanfunctioncannowbeimplemented.WeusetheexportedscanfunctionandlistenforPromisetoresolveorreject.WhenPromiseresolves,weaddtheitemtothelistandopenthedetailspage.

Wecanimporttheplugininsrc/app.ts:

import*asbarcodescannerfrom"nativescript-barcodescanner";

Thescanfunctioncannowberewrittenasfollows:

functionscan(){

barcodescanner.scan().then(result=>{

addItem(result.text);

returnfalse;

});

}

Wecanalsoshowamessagewhenthescanfailed.Thisway,theusergetsfeedbackwhenthescanfailed.Wewillshowaquestionaskingwhethertheuserwantstotryagain,asshowninthefollowingscreenshot:

Thiscanbeimplementedbyreplacingthescanfunctionwiththefollowingcode:

import*asdialogsfrom"ui/dialogs";

...

functionscan(){

barcodescanner.scan().then(result=>{

addItem(result.text);

returnfalse;

},()=>{

returndialogs.confirm("Failedtoscanabarcode.Tryagain?")

}).then(tryAgain=>{

if(tryAgain){

scan();

}

});

}

Inthefirstcallback,thescanwassuccessful.Thescanisaddedtotherecentscanlistandthedetailspageshows.Inthesecondcallback,weshowthedialog.Thedialogs.confirmfunctionreturnsapromise,whichwillresolvetoboolean.Inthelastcallback,tryAgainwillbefalseifthescanwassuccessfuloriftheuserclickedontheNobutton.Itwillbetrueiftheuser

Page 884: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

clickedontheYesbutton.Inthatcase,wewillshowthebarcodescanneragain.

Tip

Whenyoureturnavalueinthesecondcallback(orcatchcallback),theresultingPromisewillresolvetothatvalue.WhenyoureturnPromise,theresultingPromisewillberesolvedorrejectedwiththevalueorerrorofthatPromise.IfyouwanttorejecttheresultingPromise,youmustusethrow.

Page 885: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingonadeviceIntheemulator,wecannottakeapictureofaQRcode;thus,wehavetotesttheapponadevice.WecandothatbyconnectingthedeviceusingaUSBcableandthenrunningtnsrunandroidortnsrunios.YoucantesttheappusingtheseQRcodes,whichcontaintext(leftimage)andaURL(rightimage).YoucanscantheQRcodesseveraltimesandnoticethelistbuildupinthemainview.Whenyourestarttheapp,youwillseethatthelistiscleared.Wewillfixthatinthenextsection.

Page 886: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingpersistentstorageWhentheuserclosesandreopenstheapp,theuserseesanemptylistofscans.Wecanmakethelistpersistentbysavingitafterascanandloadingitwhentheappstarts.Wecanusetheapplication-settingsmoduletostorethescans.Thestorageisbasedonkey-value:avalueisassignedtoaspecifickey.

Onlybooleans,numbers,andstringscanbestoredusingthismodule.Anarraycannotbestored.Instead,onecouldstorethelengthunderonekey(forinstance,items-length)andtheitemsunderasetofkeys(items-0,items-1,...).AneasierapproachistoconvertthearraytoastringusingJSON.

Thelistcanbesavedusingthefollowingfunction:

functionsave(){

applicationSettings.setString("items",JSON.stringify(items));

}

TheDateobjectsareconvertedtostringsbyJSON.stringify.Thus,wemustconvertthembacktoaDateobjectmanually:

functionload(){

constdata=applicationSettings.getString("items");

if(data){

try{

items=(<any[]>JSON.parse(data)).map(item=>({

content:item.content,

date:newDate(item.date)

}));

}catch(e){}

}

}

Beforeapplication.start(),wemustcalltheloadandupdatefunctionstoshowthepreviousscans:

constpage=createPage(index=>showDetailsPage(items[index]),scan);

application.mainEntry=page.createView;

load();

update();

application.start();

InaddItem,wemustcallsave:

functionaddItem(content:string){

constitem:model.Scan={

content,

date:newDate()

};

items=[item,...items].slice(0,100);

save();

Page 887: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

update();

showDetailsPage(item);

}

Page 888: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StylingtheappTheappcanbestyledusingCSS.NotallCSSpropertiesaresupported,butbasicsettingslikefonts,colors,margin,andpaddingwork.Wecanaddastylesheetintheappaddingthefollowingcodebeforeapplication.start():

application.cssFile="style.css";

Wewillchangethestyleofthefollowingpartsoftheapp:

Inapp/style.css,wewillfirstgivetheActionBarabackgroundcolor:

ActionBar{

background-color:#237691;

color:#fefefe;

}

Tip

Thestylesheetmustbeaddedintheappfolder,insteadofsrc.NativeScriptwillonlyloadfilesinsideapp.TypeScriptfilesarecompiledintothatfolder,butthestylesheetshouldalreadybelocatedthere.

Wewilladdsomemargintothelabelsinthelistanddetailspage:

Label{

margin:10px;

}

Themainpageisnowproperlystyled,asshowninthefollowingscreenshot:

Wecanalsostylethelabelonthedetailpage,whichwegaveaclassname.Wemakethetextinthelabelbiggerandcenterthetext:

.details-content{

font-size:28pt;

text-align:center;

margin:10px;

}

Thisresultsinthefollowingdesign:

Page 889: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management
Page 890: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ComparingNativeScripttoalternativesVariousframeworksthatcanbuildmobileappsexist.LotsofdevelopersuseCordovaorPhonegap.ThesetoolswrapanHTMLpageintoanapp.Theseappsarecalledhybrid,astheycombineHTMLpageswithmobileapps.Theuserinterfaceisnotnativeandcangiveabaduserexperience.

Othertoolshaveanativeinterface,whichgivesagoodlookandfeel.Titanium,NativeScript,andReactNativedothis.Withthesetools,lesscodecanbesharedbetweenawebappandmobileapp.WithReactNative,appscanbewrittenusingtheReactframework.

InNativeScript,programmershaveaccesstoallnativeAPIs.Thedisadvantageofthisisthattheprogrammerwouldwriteplatform-specificcode.NativeScriptalsoincludeswrappersaroundtheseclasses,whichworkonbothAndroidandiOS.Forinstance,theButtonclass,whichweusedinthischapter,isawrapperaroundandroid.widget.ButtononAndroidandUIButtononiOS.

Page 891: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wecreatedamobileappusingNativeScript.WeusedaplugintoscanQRcodes.Thescansaresaved,sothelistispersistedafterarestartoftheapp.Finally,weaddedcustomstylestoourapp.

Inthenextchapter,wewillbuildaspreadsheetwebappusingReact.Wewilldiscoversomeprinciplesoffunctionalprogrammingandlearnhowwecanhandlethestateofanapplication.Wewillalsoseehowwecanbuildacross-platformapplication.

Page 892: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter6.AdvancedProgramminginTypeScriptInthepreviouschapters,welearnedthebasicsofTypeScriptandweworkedwithvariousframeworks.WewilldiscovermoreadvancedfeaturesofTypeScriptinthischapter.Thischaptercoversthefollowingaspects:

UsingtypeguardsMoreaccuratetypeguardsCheckingnullandundefinedCreatingtaggeduniontypesComparingperformanceofalgorithms

Page 893: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingtypeguardsSometimes,youmustcheckwhetheravalueisofacertaintype.Forinstance,ifyouhaveavalueofaclassBase,youmightwanttocheckifitisofacertainsubclassDerived.InJavaScriptyouwouldwritethiswithaninstanceofcheck.SinceTypeScriptisanextensionofJavaScript,youcanalsouseinstanceofinTypeScript.Inothertypedlanguages,likeC#,youmustthenaddatypecast,whichtellsthecompilerthatavalueisofatype,differentfromwhatthecompileranalyzed.Youcanalsoaddtypecastsintwodifferentways.Theoldsyntaxfortypecastsuses<and>,thenewsyntaxusestheaskeyword.Youcanseethembothinthenextexample:

classBase{

a:string;

}

classDerivedextendsBase{

b:number;

}

constfoo:Base;

if(fooinstanceofDerived){

(<Derived>foo).b;

(fooasDerived).b;

}

Whenyouuseatypeguard,yousaytothecompiler:trustme,thisvaluewillalwaysbeofthistype.Thecompilercannotcheckthatandwillassumethatitistrue.But,weareusingacompilertogetnotifiedabouterrorssowewanttoreducetheamountofcaststhatweneed.

Luckily,thecompilercan,inmostcases,understandtheusagesofinstanceof.Thus,inthepreviousexamplethecompilerknowsthatthetypeoffooisDerivedinsidetheif-block.Thus,wedonotneedtypecaststhere:

constfoo:Base;

if(fooinstanceofDerived){

foo.b;

}

Anexpressionthatcheckswhetheravalueisofacertaintypeiscalledatypeguard.TypeScriptsupportsthreedifferentkindsoftypeguards:

Thetypeofguardchecksforprimitivetypes.Itstartstypeofx===ortypeofx!==,followedbystring,number,boolean,object,function,orsymbol.Theinstanceofguardchecksforclasstypes.Suchatypeguardstartswiththevariablename,followedbyinstanceofandtheclassname.Userdefinedtypeguardsacustomtypeguard.Youcandefineacustomtypeguardasafunctionwithaspecialreturntype:

functionisCat(animal:Animal):animalisCat{

returnanimal.name==="Kitty";

}

Page 894: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

YoucanthenuseitasisCat(x).

Youcanusethesetypeguardsintheconditionofif,while,for,anddo-whilestatementsandinthefirstoperandofbinarylogicaloperators(x&&y,x||y)andconditionalexpressions(x?y:z).

Page 895: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NarrowingThetypeofavariablewillchange(locally)afteratypeguard.Thisiscallednarrowing.Thetypewillbemorespecificafternarrowing.Morespecificcanmeanthataclasstypeisreplacedbythetypeofasubclass,orthataunionisreplacedbyoneofitsparts.Thelatterisdemonstratedinthefollowingexample:

letx:string|number;

if(typeofx==="string"){

//x:string

}else{

//x:number

}

Asyoucansee,atypeguardcanalsonarrowavariableintheelseblock.

Page 896: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NarrowinganyNarrowingwillgiveamorespecifictype.Forinstance,stringismorespecificthanany.Thefollowingcodewillnarrowxfromanytostring:

letx:any;

if(typeofx==="string"){

//x:string

}

Ingeneral,amorespecifictypecanbeusedonmoreconstructsthantheinitialtype.Forinstance,youcancall.substringonastring,butnotonastring|number.Whennarrowingfromany,thatisnotthecase.Youmaywritex.abcdifxhasthetypeany,butnotwhenitstypeisstring.Inthiscase,amorespecifictypeallowslessconstructswiththatvalue.Topreventtheseissues,thecompilerwillonlynarrowvaluesoftypeanytoprimitivetypes.Thatmeansthatavaluecanbenarrowedtostring,butnottoaclasstype,forinstance.Thenextexampledemonstratesacasewherethecompilerwouldgiveanundesirederror,ifthiswasnotimplemented:

letx:any;

if(xinstanceofObject){

x.abcd();

}

Intheblockafterthetypeguard,xshouldnotbenarrowedtoObject.

Page 897: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CombiningtypeguardsTypeguardscanbecombinedintwoways.First,youcannestifstatementsandthusapplymultipletypeguardstoavariable:

letx:string|number|boolean;

if(typeofx!=="string"){

if(typeofx!=="number"){

//x:boolean

}

}

Secondly,youcanalsocombinetypeguardswiththelogicaloperators(&&,||).Thepreviousexamplecanalsobewrittenas:

letx:string|number|boolean;

if(typeofx!=="string"&&typeofx!=="number"){

//x:boolean

}

With||,wecancheckthatavaluematchesoneofmultipletypeguards:

letx:string|number|boolean;

if(typeofx==="string"||typeofx==="number"){

//x:string|number

}else{

//x:boolean

}

Morecomplextypeguardscanbecreatedwithuserdefinedtypeguards.

Page 898: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MoreaccuratetypeguardsBeforeTypeScript2.0,thecompilerdidnotusethecontrolflowoftheprogramfortypeguards.Theeasiestwaytoseewhatthatmeans,isbyanexample:

functionf(x:string|number){

if(typeofx==="string"){

return;

}

x;

}

Thetypeguardnarrowsxtostringintheblockaftertheifstatement.Iftheelseblockexisted,itwouldhavenarrowedxtonumberthere.Outsideoftheifstatement,nonarrowinghappens,becausethecompileronlylooksatthestructureorshapeoftheprogram.Thatmeansthatthetypeofxonthelastlinewouldbestring|number,eventhoughthatlinecanonlybeexecutediftheconditionoftheifstatementisfalseandxcanonlybeanumberthere.Withsometerminology,typeguardswereonlysyntaxdirectedandwereonlybasedonthesyntax,notonthecontrolflowoftheprogram.

AsofTypeScript2.0,thecompilercanfollowthecontrolflowoftheprogram.Thisgivesmoreaccuratetypesaftertypeguards.Thecompilerunderstandsthatthelastlineofthefunctioncanonlybereachedifxisnotastring.Thetypeofxonthelastlinewillnowbenumber.Thisanalysisiscalledcontrolflowbasedtypeanalysis.

Page 899: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AssignmentsPreviously,thecompilerdidnotfollowassignmentsofavariable.Ifavariablewasreassignedintheblockafteranifstatement,thenarrowingwouldnotbeapplied.Thus,inthenextexample,thetypeofxisstring|number,bothbeforeandaftertheassignment:

letx:string|number=...;

if(typeofx==="string"){

x=4;

}

Withcontrolflowbasedtypeanalysis,theseassignmentscanbechecked.Thetypeofxwillbestringbeforetheassignmentandnumberafterit.Narrowingafteranassignmentworksonlyforuniontypes.Thepartsoftheuniontypearefilteredbasedontheassignedvalue.Fortypesotherthanuniontypes,thetypeofthevariablewillberesettotheinitialtypeafteranassignment.

Thiscanbeusedtowriteafunctionthateitheracceptsonevalueoralistofvalues,inoneofthefollowingways:

functionf(x:string|string[]){

if(typeofx==="string")x=[x];

//x:string[]

}

functiong(x:string|string[]){

if(xinstanceofArray){

for(constitemofx)g(item);

return;

}

//x:string

}

Withthesameanalysis,thecompilercanalsocheckforvaluesthatarepossiblynullorundefined.Insteadofgettingruntimeerrorssayingundefinedisnotanobject,youwillgetacompiletimewarningthatavariablemightbeundefinedornull.

Page 900: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CheckingnullandundefinedTypeScript2.0introducestwonewtypes:nullandundefined.YouhavetosetthecompileroptionstrictNullCheckstotruetousethesetypes.Inthismode,allothertypescannotcontainundefinedornullanymore.Ifyouwanttodeclareavariablethatcanbeundefinedornull,youhavetoannotateitwithauniontype.Forinstance,ifyouwantavariablethatshouldcontainastringorundefined,youcandeclareitasletx:string|undefined;.

Beforeassignments,thetypeofthevariablewillbeundefined.Assignmentsandtypeguardswillmodifythetypelocally.

Page 901: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GuardagainstnullandundefinedTypeScripthasvariouswaystocheckwhetheravariablecouldbeundefinedornull.Thenextcodeblockdemonstratesthem:

letx:string|null|undefined=...;

if(x!==null){

//x:string|undefined

}

if(x!==undefined){

//x:string|null

}

if(x!=null){

//x:string

}

if(x){

//x:string

}

Thelasttypeguardcanhaveunexpectedbehavior,soitisadvisedtousetheothersinstead.Atruntime,xisconvertedtoaBoolean.nullandundefinedarebothconvertedtofalse,non-emptystringstotrue,butanemptystringisconvertedtofalse.Thelatterisnotalwaysdesired.

Tocheckforastring,youcanalsousetypeofx==="string"asatypeguard.Itisnotalwayspossibletowriteatypeguardforsometypes,butyoucanalwaysusethetypeguardsinthecodeblock.

Page 902: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ThenevertypeTypeScript2.0alsointroducedthenevertype,whichrepresentsanunreachablevalue.Forinstance,ifyouwriteafunctionthatalwaysthrowsanerror,itsreturntypewillbenever.

functionalwaysThrows(){

thrownewError();

}

Inauniontype,neverwilldisappear.Formally,T|neverandnever|TareequaltoT.Youcanusethistocreateanassertionthatacertainpositioninyourcodeisunreachable:

functionunreachable(){

thrownewError("Shouldbeunreachable");

}

functionf(){

switch(...){

case...:

returntrue;

case...:

returnfalse;

default:

returnunreachable();

}

Thecompilertakestheunionofthetypesofallexpressionsinreturnstatements.Thatgivesboolean|neverinthisexample,whichisreducedtoboolean.

WewillusestrictNullChecksinthenextchapters.

Page 903: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtaggeduniontypesWithTypeScript2.0,youcanaddatagtouniontypesandusetheseastypeguards.Thatfeatureiscalled:discriminateduniontypes.Thissoundsverydifficult,butinpracticeitisveryeasy.Thefollowingexampledemonstratesit:

interfaceCircle{

type:"circle";

radius:number;

}

interfaceSquare{

type:"square";

size:number;

}

typeShape=Circle|Square;

functionarea(shape:Shape){

if(shape.type==="circle"){

returnshape.radius*shape.radius*Math.PI;

}else{

returnshape.size*shape.size;

}

}

Theconditionintheifstatementsworksasatypeguard.Itnarrowsthetypeofshapetocircleinthetruebranchandsquareinthefalsebranch.

Tousethisfeature,youmustcreateauniontypeofwhichallelementshaveapropertywithastringvalue.Youcanthencomparethatpropertywithastringliteralandusethatasatypeguard.Youcanalsodothatcheckinaswitchstatement,likethenextexample.

functionarea(shape:Shape){

switch(shape.type){

case"circle":

returnshape.radius*shape.radius*Math.PI;

case"square":

returnshape.size*shape.size;

}

}

WehavenowseenthenewmajorfeaturesofthetypesystemofTypeScript2.0.Wewillseemostoftheminactioninthenextchapters.Wewillalsowritesomesimplealgorithmsinthesechapters.Wewilllearnsomebackgroundinformationonwritingandanalyzingalgorithmsinthenextsection.

Page 904: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ComparingperformanceofalgorithmsWewillalsowritesomesmallalgorithmsinthenextchapters.Thissectionshowshowtheperformanceofanalgorithmcanbeestimated.Duringsuchanalysis,itisoftenassumedthatonlyalargeinputgivesperformanceproblems.Theanalysiswillshowhowtherunningtimescaleswhentheinputscales.

Thenextsectionrequiressomeknowledgeofbasicmathematics.However,thissectionisnotforeknowledgeforthenextchapters.Ifyoudonotunderstandapieceofthissection,youcanstillfollowtherestofthebook.

Forinstance,ifyouwanttofindtheindexofanelementinalist,youcanuseaforloop:

functionindexOf(list:number[],item:number){

for(leti=0;i<list.length;i++){

if(list[i]===item)returni;

}

return-1;

}

Thisfunctionloopsoverallelementsofthearray.Ifthearrayhassizen,thenthebodyoftheloopwillbeevaluatedntimes.Wedonotknowhowlongthebodyoftheloopruns.Itcouldbehundredsortensofasecond,butthatdependsonthecomputer.Whenyouruntheprogramtwice,thetimewillprobablynotbeexactlythesame.

Luckily,wedonotneedthesenumbersfortheanalysis.Itisimportanttoseethattherunningtimeofthebodydoesnotdependonthesizeofthearray.

Thefunctionfirstsetsito0,andthenexecutesthecodeintheloopatmostntimes.Intheworstcase,thebodyisexecutedntimesandthefinalreturn-1runs.Therunningtimewillthenbesomething+n*something+something,whereallinstancesofsomethingdonotdependonn.Whentheinputisbigenough,wecanneglectthetimeoftheinitializationofiandthefinalreturn-1.So,foralargen,therunningtimeisapproximatelyn*something.

Page 905: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Big-OhnotationMathematicianscreatedanotationtowritethismoresimply,calledBig-Ohnotation.WhenyousaythattherunningtimeisO(n),youmeanthattherunningtimeisatmostn*something,forabigenoughn.Ingeneral,O(f(n)),wheref(n)isaformula,meansthattherunningtimeisatmostamultipleoff(n).Moreformally,ifyousaythattherunningtimeisO(f(n)),youmeanthatforsomenumbersNandcthefollowingholds:ifn>Nthentherunningtimeisatmostc*f(n).Theconditionn>Nisaformalwayofsaying:ifnisbigenough,thevalueofcisthereplacementofsomething.

Fortheoriginalproblem,thiswouldresultinO(n).Whenyouanalyzesomeotheralgorithms,youcancounthowoftenitcanbeexecutedforeachpieceofcode.Fromtheseterms,youmustchoosethehighestone.Wewillanalyzethenextexample:

functionhasDuplicate(items:number[]){

for(leti=0;i<items.length;i++){

for(letj=0;j<items.length;j++){

if(items[i]===items[j]&&i!==j)returntrue;

}

}

returnfalse;

}

Thefirstline,whereiisdeclared,isonlyevaluatedonce.Thelinewherejisdeclaredisexecutedatmostntimes,becauseitisinthefirstforloop.Theifstatementrunsatmostn*n,orn2times.Thelastlineisevaluatedatmostonce.Thehighesttermoftheseisn2.Thus,thisalgorithmrunsinO(n2).

Page 906: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

OptimizingalgorithmsForalargearray,thisfunctionmightbetooslow.Ifwewouldwanttooptimizethisalgorithm,wecanmakethesecondforloopshorter.

functionhasDuplicate(items:number[]){

for(leti=0;i<items.length;i++){

for(letj=0;j<i;j++){

if(items[i]===items[j])returntrue;

}

}

returnfalse;

}

Withtheoldversion,wewouldcompareeverytwoitemstwice,butnowwecomparethemonlyonce.Wealsocanremovethechecki!==j.Itrequiressomemoreworktoanalyzethisalgorithm.Thebodyofthesecondforloopisnowevaluated0+1+2+...+(n-1)times.Thisisasumofntermsandtheaverageofthetermsis(n-1)/2.Thisresultsinn*(n-1)/2,orn2/2-n/2.WiththeBig-Ohnotation,youcanwritethisasO(n2).Thisisthehighestterm,sothewholealgorithmstillrunsinO(n2).Asyoucansee,thereisnodifferenceintheBig-Ohbetweentheoriginalandoptimizedversions.Thealgorithmwillbeabouttwiceasfast,butiftheoriginalalgorithmwaswaytooslow,thisoneisprobablytooslow.Realoptimizationisabithardertofind.

Page 907: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BinarysearchWewillfirsttakealookattheindexOfexample,whichrunsinO(n).Whatifweknewthattheinputisalwaysasortedlist?Insuchacase,wecanfindtheindexmuchfaster.Iftheelementatthecenterofthearrayishigherthanthevaluethatwesearch,wedonothavetotakealookatallelementsontherightsideofthearray.Ifthevalueatthecenterislower,thenwecanforgetallelementsontheleftside.Thisiscalledbinarysearch.Wecanimplementthiswithtwovariables,leftandright,whichrepresentthesectionofthearrayinwhichwearesearching:leftisthefirstelementofthatsection,andrightisthefirstelementafterthesection.Soright-1isthelastelementofthesection.Thecodeworksasfollows:itchoosesthecenterofthesection.Ifthatelementistheelementthatwesearch,wecanstop.Otherwise,wecheckwhetherweshouldsearchontheleftorrightside.Whenleftequalsright,thesectionisempty.Wewillthenreturn-1,sincewedidnotfindtheelement.

functionbinarySearch(items:number[],item:number){

letleft=0;

letright=items.length;

while(left<right){

constmid=Math.floor((left+right)/2);

if(item===items[mid]){

returnmid;

}elseif(item<items[mid]){

right=mid;

}else{

left=mid+1;

}

}

return-1;

}

Whatistherunningtimeofthisalgorithm?Tofindthat,wemustknowhowoftenthebodyoftheloopisevaluated.Everytimethatthebodyisexecuted,thefunctionreturns,orthelengthofthesectionisapproximatelydividedbytwo.Intheworstcase,thelengthofthesectionisconstantlydividedbytwo,untilthesectioncontainsoneelement.Thatelementisthesearchedelementandthefunctionreturnsorthelengthbecomeszeroandthefunctionwhileloopstops.So,wecanaskthequestion:howoftencanyoudividenbytwo,untilitbecomeslessthanone?2logngivesusthatnumber.ThisalgorithmrunsinO(2log(n)),whichisalotfasterthanO(n).However,itonlyworksifthearrayissorted.

Tip

InBig-Ohnotation,O(log(n))andO(2log(n))arethesame.Theyonlydifferbysomeconstantnumber,whichdisappearsinBig-Ohnotation.

Page 908: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Built-infunctionsWhenyouuseotherfunctionsinyouralgorithm,youmustbeawareoftheirrunningtime.WecouldforinstanceimplementindexOflikethis:

functionfastIndexOf(items:number[],item:number){

items.sort();

returnbinarySearch(items,item);

}

Bothlinesofthefunctionareonlyexecutedonce,butO(1)isnottherunningtimeofthisalgorithm!ThisfunctioncallsbinarySearch,andweknowthatthebodyofthewhileloopinthatfunctionruns,atmost,approximately2logntimes.Wedonotneedtoknowhowthefunctionisimplemented,weonlyneedtoknowthatittakesO(2log(n)).Wealsocall.sort()onthearray.Wehavenotwrittenthatfunctionourselvesandwecannotanalyzethecodeforit.Forthesefunctions,youmustknow(orlookup)therunningtime.Forsorting,thatisO(n2log(n)).SoourfastIndexOfisnotfasterthantheoriginalversion,asitrunsinO(n2log(n)).

WecanhoweverusesortingtoimprovethehasDuplicatefunction.

functionhasDuplicate(items:number[]){

items.sort();

for(leti=1;i<items.length;i++){

if(items[i]===items[i-1])returntrue;

}

returnfalse;

}

TheloopcostsO(n)andthesortingcostsO(n2log(n)),sothisalgorithmrunsinO(n2log(n)).Thisisfasterthanourinitialimplementation,thattookO(n2).

Withthisbasicknowledge,youcananalyzesimplealgorithmsandcomparetheirspeedsforlargeinputs.Inthenextchapters,wewillanalyzesomeofthealgorithmsthatwewillwrite.

Page 909: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wehaveseenvariousnewfeaturesofTypeScript2.0.Inthisrelease,lotsofnewfeaturesformoreaccuratetypeanalysiswereadded.Wehaveseencontrolflowbasedtypeanalysis,nullandundefinedchecking,andtaggeduniontypes.Finally,wehavealsotakenalookatanalyzingalgorithms.Wewillusemostofthesetopicsinthenextthreechapters.InChapter7,SpreadsheetApplicationwithFunctionalProgramming,wewillbuildaspreadsheetapplication.Wewillalsodiscoverfunctionalprogrammingthere.

Page 910: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter7.SpreadsheetApplicationswithFunctionalProgrammingInthischapter,wewillexploreadifferentstyleofprogramming:functionalprogramming.Withthisstyle,functionsshouldonlyreturnsomethingandnothaveothersideeffects,suchasassigningaglobalvariable.Wewillexplorethisbybuildingaspreadsheetapplication.

Userscanwritecalculationsinthisapplication.Thespreadsheetcontainsagridandeveryfieldofthegridcancontainanexpressionthatwillbecalculated.Suchexpressionscancontainconstants(numbers),operations(suchasaddition,multiplying),andtheycanreferenceotherfieldsofthespreadsheet.Wewillwriteaparser,thatcanconvertthestringrepresentationofsuchexpressionsintoadatastructure.Afterwards,wecancalculatetheresultsoftheexpressionswiththatdatastructure.Ifnecessary,wewillshowerrorssuchasdivisionbyzerototheuser.

Wewillbuildthisapplicationusingthefollowingsteps:

SettinguptheprojectFunctionalprogrammingUsingdatatypesforexpressionsWritingunittestsParsinganexpressionDefiningthesheetUsingtheFluxarchitectureCreatingactionsWritingtheviewAdvantagesofFlux

Page 911: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SettinguptheprojectWestartbyinstallingthedependenciesthatweneedinthischapterusingNPM:

npminit-y

npminstallreactreact-dom-save

npminstallgulpgulp-typescriptsmall--save-dev

WesetupTypeScriptwithlib/tsconfig.json:

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"noImplicitAny":true,

"jsx":"react"

}

}

Weconfiguregulpingulpfile.js:

vargulp=require("gulp");

varts=require("gulp-typescript");

varsmall=require("small").gulp;

vartsProject=ts.createProject("lib/tsconfig.json");

gulp.task("compile",function(){

returngulp.src(["lib/**/*.ts","lib/**/*.tsx"])

.pipe(ts(tsProject))

.pipe(gulp.dest("dist"))

.pipe(small("client/index.js",{externalResolve:["node_modules"],

outputFileName:{standalone:"client.js"}}))

.pipe(gulp.dest("static/scripts/"));

});

WeinstalltypedefinitionsforReact:

npminstall@types/react@types/react-dom--save

Instatic/index.html,wecreatetheHTMLstructureofourapplication:

<!DOCTYPEHTML>

<html>

<head>

<title>Chapter5</title>

<linkhref="style.css"rel="stylesheet"/>

</head>

<body>

<divid="wrapper"></div>

<scripttype="text/javascript">

varprocess={

env:{

Page 912: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

NODE_ENV:"DEBUG"//or"PRODUCTION"

}

};

</script>

<scripttype="text/javascript"src="scripts/client.js"></script>

</body>

</html>

Weaddsomebasicstylesinstatic/style.css.Wewilladdmorestyleslateron:

body{

font-family:'TrebuchetMS','LucidaSansUnicode','LucidaGrande','Lucida

Sans',Arial,sans-serif;

}

a:link,a:visited{

color:#5a8bb8;

text-decoration:none;

}

a:hover,a:active{

color:#406486;

}

Page 913: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

FunctionalprogrammingWhenyouaskadeveloperwhatthedefinitionofafunctionis,hewouldprobablyanswersomethinglike"somethingthatdoessomethingwithsomearguments".Mathematicianshaveaformaldefinitionforafunction:

Afunctionisarelationwhereaninputhasexactlyoneoutput.

Thismeansthatafunctionshouldalwaysreturnthesameoutputforthesameinput.Functionalprogramming(FP)usesthismathematicaldefinition.Thefollowingcodewouldviolatethisdefinition:

letx=1;

functionf(y:number){

returnx+y;

}

f(1);

x=2;

f(1);

Thefirstcalltofwouldreturn2,butthesecondwouldreturn3.Thisiscausedbytheassignmenttox,whichiscalledasideeffect.Areassignmenttoavariableorapropertyiscalledasideeffect,sincefunctioncallscangivedifferentresultsafterit.

Itwouldbeevenworseifafunctionmodifiedavariablethatwasdefinedoutsideofthefunction:

letx=1;

functiong(y:number){

x=y;

}

Codelikethisishardtoreadortest.Thesemutationsarecalledsideeffects.Whenapieceofcodedoesnothavesideeffects,itiscalledpure.Withfunctionalprogramming,allormostfunctionsshouldbepure.

Page 914: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CalculatingafactorialWewilltakealookatthefactorialfunctiontoseehowwecansurpassthelimitationsoffunctionalprogramming.Thefactorialfunction,writtenasn!isdefinedas1*2*3*...*n.Thiscanbeprogrammedwithasimpleforloop:

exportfunctionfactorial(x:number){

letresult=1;

for(leti=1;i<=x;i++){

result*=i;

}

returnresult;

}

However,thevalueofiisincreasedintheloop,whichisareassignmentandthusasideeffect.Withfunctionalprogramming,recursionshouldbeusedinsteadofaloop.Thefactorialofxcanbecalculatedusingthefactorialofx-1andmultiplyingitwithx,sincex!=x*(x-1)!forx>1.Thefollowingfunctionispureandsmallerthantheiterativefunction.Callingafunctionfromthesamefunctioniscalledrecursion.

exportfunctionfactorial(x:number):number{

if(x<=1)return1;

returnx*factorial(x-1);

}

Tip

Whenyoudefineafunctionwithrecursion,TypeScriptcannotinferthereturntype.Youhavetospecifythereturntypeyourselfinthefunctionheader.

Wewillusethisfunctionlateron,sosavethisaslib/model/utils.ts.

Page 915: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingdatatypesforexpressionsFieldsofthespreadsheetcancontainexpressions,thatcanbecalculated.Tocalculatethesevalues,theinputoftheusermustbeconvertedtoadatastructure,whichcanthenbeusedtocalculatetheresultofthatfield.

Theseexpressionscancontainconstants,operations,referencestootherfieldsoraparenthesizedexpression:

Constants:0,42,10.2,4e6,7.5e8Unaryexpression:-expression,expression!Binaryexpression:expression+expression,expression/expressionReferences:3:1(thirdcolumn,firstrow)Parenthesizedexpression:(expression)

Wewillcreatethesetypesinlib/model/expression.ts.Firstweimportfactorial,sincewewillneeditlateron.

import{factorial}from"./utils";

Page 916: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingdatatypesWecandeclaredatatypesfortheseexpressionkinds.Wedefinethemusingaclass.Wecandistinguishthesekindseasilyusinginstanceof.WecandeclareConstantasfollows:

exportclassConstant{

constructor(

publicvalue:number

){}

}

Tip

Addingpublicorprivatebeforeaconstructorargumentissyntacticsugarfordeclaringthepropertyandassigningtoitintheconstructor:

exportclassConstant{

value:number;

constructor(value:number){

this.value=value;

}

}

AUnaryExpressionhasakind(minusorfactorial)andtheoperandonwhichitisworking.Wedefinethekindusinganenum.Fortheexpression,wereferencetheExpressiontypethatwewilldefinelateron:

exportclassUnaryOperation{

constructor(

publicexpression:Expression,

publickind:UnaryOperationKind

){}

}

exportenumUnaryOperationKind{

Minus,

Factorial

}

Abinaryexpressionalsohasakind(Add,Subtract,Multiply,orDivide)andtwooperands.

exportclassBinaryOperation{

constructor(

publicleft:Expression,

publicright:Expression,

publickind:BinaryOperationKind

){}

}

exportenumBinaryOperationKind{

Add,

Subtract,

Multiply,

Divide

}

Page 917: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wewillcallthereferencetoanotherfield,aVariable.Itcontainsthecolumnandtherowofthereferencedfield:

exportclassVariable{

constructor(

publiccolumn:number,

publicrow:number

){}

}

Aparenthesizedexpressionsimplycontainsanexpression:

exportclassParenthesis{

constructor(

publicexpression:Expression

){}

}

WecannowdefineExpressionastheuniontypeoftheseclasses:

exporttypeExpression=Constant|UnaryOperation|BinaryOperation|Variable

|Parenthesis;

TheprecedingdefinitionmeansthatanExpressionisaConstant,UnaryExpression,BinaryExpression,VariableorParenthesis.

Page 918: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TraversingdatatypesWecandistinguishtheseclassesusinginstanceof.Wewilldemonstratethatbywritingafunctionthatconvertsanexpressiontoastring.TypeScriptwillchangethetypeofavariableafteraninstanceofcheck.Thesechecksarecalledtypeguards.Inthecodebelow,formulainstanceofConstantnarrowsthetypeofformulatoConstantintheblockaftertheif.Intheelseblock,Constantisremovedfromthetypeofformula,resultinginUnaryOperation|BinaryOperation|Variable|Parenthesis.

Usingasequenceofifstatements,wecandistinguishallcases.Foraconstant,wecansimplyconvertthevaluetoastring:

exportfunctionexpressionToString(formula:Expression):string{

if(formulainstanceofConstant){

returnformula.value.toFixed();

ForaUnaryOperation,weshowtheoperatorbeforeoraftertherestoftheexpression.WeconverttheresttoastringusingrecursionandwecallexpressionToStringontheexpression.Becauseofthat,wehadtospecifythereturntypemanually:

}elseif(formulainstanceofUnaryOperation){

const{expression,kind}=formula;

switch(kind){

caseUnaryOperationKind.Factorial:

returnexpressionToString(expression)+"!";

caseUnaryOperationKind.Minus:

return"-"+expressionToString(expression);

}

WeconvertaBinaryOperationtoastringbyinsertingtheoperatorbetweentheconvertedoperands:

}elseif(formulainstanceofBinaryOperation){

const{left,right,kind}=formula;

constleftString=expressionToString(left);

constrightString=expressionToString(right);

switch(kind){

caseBinaryOperationKind.Add:

returnleftString+"+"+rightString;

caseBinaryOperationKind.Subtract:

returnleftString+"-"+rightString;

caseBinaryOperationKind.Multiply:

returnleftString+"*"+rightString;

caseBinaryOperationKind.Divide:

returnleftString+"/"+rightString;

}

Avariableisshownasthecolumn,acolonandtherow:

}elseif(formulainstanceofVariable){

const{column,row}=formula;

returncolumn+":"+row;

Page 919: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Aparenthesizedexpressionisshownasthecontainingexpressionwrappedinparentheses:

}elseif(formulainstanceofParenthesis){

const{expression}=formula;

return"("+expressionToString(expression)+")";

}

}

Thisfunctionisagoodexampleofwalkingthrough(traversing)adatastructurewithrecursion.Suchafunctioncanbewritteninthefollowingsteps:

Distinguishdifferentcases(forinstanceusinginstanceofortypeof)Handlethecontainingnodesrecursively(forinstance,leftandrightofaBinaryOperation)Combinetheresults

Inthenextsession,wewillwriteanotherfunctionthattraversesanexpressiontovalidateit.

Page 920: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ValidatinganexpressionWhenyouarewritingafunctionwithrecursion,youshouldalwaysbesurethatyouarenotcreatinginfiniterecursion,similartoaninfiniteloop.Forinstance,whenyouforgetthebasecasesofthefactorialfunction(x<=1),youwouldgetinfiniterecursion.

Wewouldalsogetrecursionwhenafieldofthespreadsheetreferencesitself(directlyorindirectly).Topreventtheseissues,wewillvalidateanexpressionbeforecalculatingit.Wecreatetherestrictionthatareferenceshouldnotpointtoitselfanditmaynotreferenceahighercolumnorrowindex.

Lateron,wewillalsoshowerrorswhenanumberisdividedbyzero,whenthefactorialofanegativeornon-integeriscalculated,whenareferencedfieldcontainsanerror,andwhenareferencedfieldcontainstextinsteadofanumber.WedefineaclassFailuretorepresentsuchanerror:

exportclassFailure{

constructor(

publickind:FailureKind,

publiclocation:Expression

){}

}

exportenumFailureKind{

ForwardReference,

SelfReference,

TextNotANumber,

DivideByZero,

FactorialNegative,

FactorialNonInteger,

FailedDependentRow

}

Next,wedefineafunctionwhichgivesastringdescriptionoftheerror:

exportfunctionfailureText({kind}:Failure){

switch(kind){

caseFailureKind.ForwardReference:

return"Thisexpressioncontainsaforwardreferencetoanother

variable";

caseFailureKind.SelfReference:

return"Thisexpressionreferencesitself";

caseFailureKind.TextNotANumber:

return"Thisexpressionreferencesafieldthatdoesnotcontaina

number";

caseFailureKind.DivideByZero:

return"Cannotdividebyzero";

caseFailureKind.FactorialNegative:

return"Cannotcomputethefactorialofanegativenumber";

caseFailureKind.FactorialNonInteger:

return"Thefactorialcanonlybecomputedofaninteger";

caseFailureKind.FailedDependentRow:

return"Thisexpressionreferencesafieldthathasoneormore

Page 921: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

errors";

}

}

Nowwecandefineavalidatefunction,whichwillgenerateanarrayoferrors.Thefunctionhastwobasecases:constantsandvariables.

Aconstantcanneverhaveerrors.Avariableisanerrorifitisaselforforwardreference.Foraunary,binary,orparenthesizedexpressionwemustvalidatethechildrenrecursively:

exportfunctionvalidate(column:number,row:number,formula:Expression):

Failure[]{

if(formulainstanceofUnaryOperation||formulainstanceofParenthesis){

returnvalidate(column,row,formula.expression);

}elseif(formulainstanceofBinaryOperation){

return[

...validate(column,row,formula.left),

...validate(column,row,formula.right)

];

}elseif(formulainstanceofVariable){

if(formula.column===column&&formula.row===row){

return[newFailure(FailureKind.SelfReference,formula)];

}

if(formula.column>column||formula.row>row){

return[newFailure(FailureKind.ForwardReference,formula)];

}

return[];

}else{

return[];

}

}

Inthefirstifstatement,thetypeofformulaisUnaryOperation|Parenthesis.Sincebothtypeshavethepropertyexpression,wecanaccessit.

Page 922: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CalculatingexpressionsThelasttraversaliscalculatingtheexpression.Thisfunctionwillreturnanumberifthecalculationsucceeded.Otherwise,itwillreturnalistoferrors.Theargumentsofthefunctionaretheexpressionandafunctionthatgivesthevalueofareferencedfield:

exportfunctioncalculateExpression(formula:Expression,resolve:(variable:

Variable)=>number|Failure[]):number|Failure[]{

Foraconstant,wecansimplyreturnitsvalue:

if(formulainstanceofConstant){

returnformula.value;

TocalculatethevalueofaUnaryOperation,wefirstcalculateitsoperand.Ifthatcontainsanerror,wepropagateit.Otherwise,wecalculatethefactorialorthenegativevalueofit.Forafactorialwealsoshowanerrorifitisnotanon-negativeinteger.Becauseofthetypeguard,TypeScriptnarrowsthetypeofvaluetoanumberintheelseblock:

}elseif(formulainstanceofUnaryOperation){

const{expression,kind}=formula;

constvalue=calculateExpression(expression,resolve);

if(valueinstanceofArray){

returnvalue;

}else{

switch(kind){

caseUnaryOperationKind.Factorial:

if(value<0){

return[newFailure(FailureKind.FactorialNegative,

formula)];

}

if(Math.round(value)!==value){

return[newFailure(FailureKind.FactorialNonInteger,

formula)];

}

returnfactorial(Math.round(value));

caseUnaryOperationKind.Minus:

return-value;

}

}

Forabinaryoperation,wecalculatetheleftandrightside.Ifoneofthesecontainserrors,wereturnthose.Otherwiseweapplytheoperatortobothvalues:

}elseif(formulainstanceofBinaryOperation){

const{left,right,kind}=formula;

constleftValue=calculateExpression(left,resolve);

constrightValue=calculateExpression(right,resolve);

if(leftValueinstanceofArray){

if(rightValueinstanceofArray){

return[...leftValue,...rightValue];

}

returnleftValue;

Page 923: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}elseif(rightValueinstanceofArray){

returnrightValue;

}else{

switch(kind){

caseBinaryOperationKind.Add:

returnleftValue+rightValue;

caseBinaryOperationKind.Subtract:

returnleftValue-rightValue;

caseBinaryOperationKind.Multiply:

returnleftValue*rightValue;

caseBinaryOperationKind.Divide:

if(rightValue===0){

return[newFailure(FailureKind.DivideByZero,

formula)];

}

returnleftValue/rightValue;

}

}

Foravariable,wedelegatethecalculationtotheresolvefunction:

}elseif(formulainstanceofVariable){

returnresolve(formula);

}elseif(formulainstanceofParenthesis){

returncalculateExpression(formula.expression,resolve);

}

}

Finally,wecalculatethevalueofaparenthesizedexpressionwiththeexpressionitcontains.

Page 924: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParsinganexpressionAparsercanconvertastringtosomedatatype.Thefirstguessofthetypeofaparserwouldbe:

typeParser<T>=(source:string)=>T;

Sincewewillalsouseaparsertoparseapartofthesource.Forinstance,whenparsingafactorial,wefirstparsetheoperand(whichhopefullyhasonecharacterremaining,theexclamationmark)andthenparsetheexclamationmark.Thus,aparsershouldreturntheresultingdataandtheremainingsource:

typeParser<T>=(source:string)=>[T,string];

Aconstant(suchas5.2)andavariable(5:2)bothstartwithanumber.Becauseofthat,aparsershouldreturnanarraywithalloptions:

typeParser<T>=(source:string)=>[T,string][];

Todemonstratehowthisworks,imaginethattherearetwoparsers:onethatparsesA,onethatparsesAAandonethatparsesAB.ThestringAAAcouldbeparsedwithasequenceoftheseparsersinthreedifferentways:A-A-A,A-AA,andAA-A.NowimaginethattheparserscanfirstparseAorAA,andthenonlyAB.WewillparseAAB.Thefirstpartwouldresultinthefollowingresult:

[

["A","AB"]

["AA","B"]

]

Theremainingstringofthefirstelement(AB),canthenbeparsedbythesecondparser(AB).Thiswouldhaveanemptystringastheremainingpart.Theremainingstringoftheseconditem(B)cannotbeparsed.Thus,theseparsescanparseAABasA-AB.

Page 925: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingcoreparsersWewillfirstcreatetwocoreparsersinlib/model/parser.ts.Thefunctionparserunsaparserandreturnstheresultifsuccessful,epsilonwillalwayssucceedandtokenwilltrytoparseaspecificstring.Thevaluecanbespecifiedasthelastargumentforbothfunctions:

typeParseResult<T>=[T,string][];

typeParser<T>=(source:string)=>ParseResult<T>;

exportfunctionparse<U>(parser:Parser<U>,source:string):U|undefined{

constresult=parser(source)

.filter(([result,rest])=>rest.length===0)[0];

if(!result)returnundefined;

returnresult[0];

}

constepsilon=<U>(value:U):Parser<U>=>source=>

[[value,source]];

consttoken=<U>(term:string,value:U):Parser<U>=>source=>{

if(source.substring(0,term.length)===term){

return[[value,source.substring(term.length)]];

}else{

return[];

}

};

Wewillcombinethesecoreparsersintomorecomplexandusefulparsers.First,wewillcreateafunctionthattriesdifferentparsers:

constor=<U>(...parsers:Parser<U>[]):Parser<U>=>source=>

(<[U,string][]>[]).concat(...parsers.map(parser=>parser(source)));

Wecanusethistoparseadigit.Wecombinetheparsersthatparsethenumber0t09:

constparseDigit=or(

token("0",0),token("1",1),

token("2",2),token("3",3),

token("4",4),token("5",5),

token("6",6),token("7",7),

token("8",8),token("9",9)

);

Tip

Functionsthathavefunctionsasanargumentorreturntypearecalledhighorderfunctions.Thesefunctionscaneasilybereused.Withfunctionalprogramming,youoftencreatesuchfunctions.

Page 926: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RunningparsersinasequenceAnotherwaytocombineparsersisrunningtheminasequence.Beforewecanwritethesefunctions,wemustdefinetwohelperfunctionsinlib/model/utils.ts.flattenwillconvertanarrayofarraysintoanarray.flatMapwillfirstcallmaponthearrayandsecondlyflatten:

exportfunctionflatten<U>(source:U[][]){

return(<U[]>[]).concat(...source);

}

exportfunctionflatMap<U,V>(source:U[],callback:(value:U)=>V[]):V[]{

returnflatten(source.map(callback));

}

Backinlib/model/parser.ts,wedefineamapfunction,whichcanconvertaParser<U>toaParser<V>:

constmap=<U,V>(parser:Parser<U>,callback:(value:U)=>V):Parser<V>=>

source=>

parser(source).map<[V,string]>(([item,rest])=>[callback(item),rest]);

Wealsodefineabindfunction,whichwillrunaparserafteranotherparser:

constbind=<U,V>(parser:Parser<U>,callback:(value:U)=>Parser<V>):

Parser<V>=>source=>

flatMap(parser(source),([result,rest])=>callback(result)(rest));

Withfunctionalprogramming,thetypeofafunctioncansometimesalreadydescribetheimplementation.Whentheimplementationgivesnotypeerrors,theimplementationisinmostcasescorrect.

Nextup,wecreatetwofunctionsthatcanruntwoorthreeparsersinasequenceandcancombinetheresultsoftheseparsersintoaspecifictype:

constsequence2=<U,V,W>(

left:Parser<U>,

right:Parser<V>,

combine:(x:U,y:V)=>W)=>

bind(left,x=>map(right,y=>combine(x,y)));

constsequence3=<U,V,W,T>(

first:Parser<U>,

second:Parser<V>,

third:Parser<W>,

combine:(x:U,y:V,z:W)=>T)=>

bind(first,x=>sequence2(second,third,(y,z)=>combine(x,y,z)));

Withthesefunctions,wecanwriteafunctionthatcanmatchasequenceofanylength,oralist.Alistiseitheroneelementoroneelementfollowedbyalist.Asyoucansee,thisrequiresrecursion.Weneedtheresultingparserinsidethedefinitionoftheparser,whichisnotpossible.Instead,wecancreateafunctionthatwillevaluatetheparser(source=>parser(source)):

Page 927: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

functionlist<U>(parseItem:Parser<U>){

constparser:Parser<U[]>=or(

map(parseItem,item=>[item]),

sequence2(

parseItem,

source=>parser(source),

(item,items)=>[item,...items]

)

);

returnparser;

}

Wecanalsocreateaseparatedlistparser,whichwilleitherparseonlyoneelement,orparsethefirstelementandalistofseparatorsanditems.Wecreateaninterfacetostoretheresultofthefunction:

interfaceSeparatedList<U,V>{

first:U;

items:[V,U][];

}

constseparatedList=<U,V>(parseItem:Parser<U>,parseSeparator:Parser<V>)=>

or(

map(parseItem,first=>({first,items:[]})),

sequence2(

parseItem,

list(sequence2(parseSeparator,parseItem,(sep,item)=><[V,U]>[sep,

item])),

(first,items)=>({first,items})

)

);

Wecannowparsealistofdigits:

constparseDigits=list(parseDigit);

Thiscanparsealistofdigits.Wecanconvertthattoanumberwiththemapfunctionthatwehavedefined.Sinceanintegercanbewrittenas1337=1*10^3+3*10^2+3*10^1+7*10^0.Wecanusethereducefunctionofarraysforthis.reduceworksasfollows:[1,2,3,4].reduce(f,0)===f(f(f(f(0,1),2),3),4)

Wecannowdefinetheconversionfunction:

consttoInteger=(digits:number[])=>digits.reduce(

(previous,current,index)=>

previous+current*Math.pow(10,digits.length-index-1),

0

);

Withmap,wecandefineparseInteger:

constparseInteger=map(parseDigits,toInteger);

Avariablecanbeparsedasasequenceofaninteger(thecolumn),acolon,andanotherinteger

Page 928: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

(therow):

constparseVariable=sequence3(parseInteger,token(":",undefined),

parseInteger,

(column,separator,row)=>newVariable(column,row));

Page 929: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ParsinganumberAnumberorconstantcanbewritteninthefollowingways:

8(integer)8.5(withdecimalpart)8e4=80000(withexponent)8.5e4=85000(withdecimalpartandexponent)

Wecreatetwoparsers,thatwillparsethedecimalpartandexponentofanumber.Theyfallbacktoadefaultvalue(0and1)incasethenumberdoesnothaveadecimalpartorexponent:

constparseDecimal=or(

epsilon(0),

sequence2(

token(".",undefined),

parseDigits,

(dot,digits)=>toInteger(digits)/Math.pow(10,digits.length)

)

);

constparseExponent=or(

epsilon(1),

sequence2(

token("e",undefined),

parseDigits,

(e,digits)=>Math.pow(10,toInteger(digits))

)

);

Withthesefunctions,wecaneasilydefinetheparseConstantfunction:

constparseConstant=sequence3(

parseInteger,

parseDecimal,

parseExponent,

(int,decimal,exp)=>newConstant((int+decimal)*exp)

);

WecannowdefineaparsercalledparseConstantVariableOrParenthesis,whichwillparseaconstant,variable,orparenthesizedexpression(asthenamesuggests).parseParenthesiswillbeimplementedlateron:

constparseConstantVariableOrParenthesis=or(parseConstant,parseVariable,

parseParenthesis);

Page 930: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

OrderofoperationsWhenevaluatinganexpression,theorderofexecutionisimportant.Forinstance,(3*4)+2equals14,while3*(4+2)equals18.Thecorrectevaluationof3*4+2isthefirstone.Anexpressionshouldbeevaluatedinthisorder:

1. Parenthesis2. Multiplicationanddivision3. Additionandsubtraction4. Unaryexpressions

Multipleinstancesofthesamegroupshouldbeevaluatedfromlefttoright,so10-2+3=(10-2)+3.

Twowaysexisttoimplementthis:parsingthesourceintherightorder,orparsingitlefttorightandcorrectingitduringcalculation.Sincewealreadywrotethecalculationpart,wewillparsethesourceintherightorder.Thatisalsotheeasiestoption.

Basedontheserules,theleftorrightsideofamultiplicationordivisioncanneverbeanadditionorsubtraction.Theoperandofaunaryexpressioncanonlybeaconstant,variable,orparenthesizedexpression.Withtheserestrictions,onecancreatethefollowingabstractrepresentation:

Expression←Term|Expression('+'|'-')Term

Term←Factor|Term('*'|'/')Factor

Factor←ConstantVariableOrParenthesis|'-'ConstantVariableOrParenthesis|

ConstantVariableOrParenthesis'!'

Parenthesis←'('Expression')'

ConstantVariableOrParenthesis←Constant|Variable|Parenthesis

Thismeansthatanexpressioniseitherasingleterm,oranadditionandsubtractionofmultipleterms.Atermisafactororamultiplicationanddivisionoffactors.Afactorcanbeaconstant,variableorparenthesizedexpression,optionallywithaminusoranexclamationmark.Withtheserules,anexpressionwillalwaysbeparsedintherightorder.

Wecaneasilyconvertthisabstractrepresentationtoparsers.WestartwithparseFactor,whichcanbebuiltwithorandsequence2.

constparseFactor=or(

parseConstantVariableOrParenthesis,

sequence2(

token("-",undefined),

parseConstantVariableOrParenthesis,

(t,value)=>newUnaryOperation(value,UnaryOperationKind.Minus)

),

sequence2(

parseConstantVariableOrParenthesis,

token("!",undefined),

(value)=>newUnaryOperation(value,UnaryOperationKind.Factorial)

Page 931: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

)

);

WecanimplementparseTermandparseExpressionusingthefunctionseperatedList.WewillusereducetotransformthearrayintoaBinaryOperation,justlikeweusedittoconvertanarrayofnumbersintoasinglenumberintoInteger.First,wecreatethefunctionthattransformsthearrayintoaBinaryOperation.

functionfoldBinaryOperations({first,items}:SeparatedList<Expression,

BinaryOperationKind>){

returnitems.reduce(fold,first);

functionfold(previous:Expression,[kind,next]:[BinaryOperationKind,

Expression]){

returnnewBinaryOperation(previous,next,kind);

}

}

WeusethatfunctioninparseTermandparseExpression.

constparseTerm=map(

separatedList(

parseFactor,

or(

token("*",BinaryOperationKind.Multiply),

token("/",BinaryOperationKind.Divide)

)

),

foldBinaryOperations

);

exportconstparseExpression=map(

separatedList(

parseTerm,

or(

token("+",BinaryOperationKind.Add),

token("-",BinaryOperationKind.Subtract)

)

),

foldBinaryOperations

);

WehavenotdefinedparseParenthesisyet.BecauseitdependsonparseExpression,wemustplaceitbelowitsdefinition.However,ifwewoulddefineitherewithconst,itcannotbereferencedinparseConstantVariableOrParenthesis.Insteadwewilldefineitasafunction.

functionparseParenthesis(source:string):ParseResult<Expression>{

returnsequence3(

token("(",undefined),

parseExpression,

token(")",undefined),

(left,expression,right)=>newParenthesis(expression)

)(source);

}

Page 932: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Functionscanbeusedbeforetheirdefinition.Weaddthesourceasanargument,asdefinedintheParsertype.

Page 933: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DefiningthesheetAspreadsheetwillbeagridoffields.Everyfieldcancontainastringoranexpression,asdemonstratedinthefollowingscreenshot:

Inlib/model/sheet.ts,wewilldefinethesheetandcreatefunctionstoparse,showandcalculateallexpressionsinthefield.

First,wewillimporttypesandfunctionsthatwewilluseinthisfile.

import{Expression,Variable,calculateExpression,Constant,Failure,

FailureKind,validate,expressionToString}from"./expression";

import{parse,parseConstant,parseExpression}from"./parser";

Wecandefineafieldasanexpressionorastring,andasheetasagridoffields:

exporttypeField=Expression|string;

exportclassSheet{

constructor(

publictitle:string,

publicgrid:Field[][]

){}

}

Nowwewillwritefunctionsthatgivetheamountofcolumnsandrowsofthesheet.

exportfunctioncolumns(sheet:Sheet){

returnsheet.grid.length;

}

exportfunctionrows(sheet:Sheet){

constfirstColumn=sheet.grid[0];

if(firstColumn)returnfirstColumn.length;

return0;

}

Theusercanwritetextoranexpressioninthefieldsofthespreadsheet.Whenthecontentofafieldstartswithanequalstoken,itisconsideredanexpression.WewriteafunctionparseFieldthatparsesthecontenttoanexpressionifitstartswiththeequalstoken.Otherwise,itwillreturnthestringas-is.

exportfunctionparseField(content:string):Field{

if(content.charAt(0)==="="){

returnparse(parseExpression,content.substring(1));

}else{

returncontent;

Page 934: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

}

Wealsocreateafunctionthatchangesafieldtoastring.

exportfunctionfieldToString(field:Field){

if(typeoffield==="string"){

returnfield;

}else{

return"="+expressionToString(field);

}

}

Incaseofanexpression,itconvertsittoastringandaddsanequalstokenbeforeit.Otherwise,itjustreturnsthestring.

Page 935: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CalculatingallfieldsWewillwriteafunctionthatcalculatesallexpressionsinthespreadsheet.Afieldthatcontainsanexpressionisconvertedtoanumber,ifthecalculationsucceeded,oranarrayoferrorsotherwise.Afieldthatcontainstextdoesnotneedcalculation,sothecontentisimmediatelyreturned.

Thisyieldsthistypefortheresultofthecalculation:

exporttypeResult=ResultField[][];

exporttypeResultField=string|number|Failure[];

Wewillusetwonestedloopstoloopovereachfield.Thisisnotpure,butitmakesiteasiertoresolvevariablesinexpressions.Whenavalidexpressionistobecalculated,thereferencedfieldswouldalreadybeevaluated.

exportfunctioncalculateSheet({grid}:Sheet){

constresult:ResultField[][]=[];

for(letcolumn=0;column<grid.length;column++){

constcolumnContent=grid[column];

result[column]=[];

for(letrow=0;row<columnContent.length;row++){

result[column][row]=calculateField(column,row);

}

}

returnresult;

Foreachfield,wefirstcheckwhetheritisastring.Ifso,wecanimmediatelyreturnit.Otherwise,wevalidatetheexpression.Iftheexpressionisinvalid,wereturntheerrorsandotherwiseweruncalculateExpressiononit.

functioncalculateField(column:number,row:number):ResultField{

constfield=grid[column][row];

if(typeoffield==="string"){

returnfield;

}else{

consterrors=validate(column,row,field);

if(errors.length!==0)returnerrors;

returncalculateExpression(field,resolveVariable);

}

}

Whenavariablereferenceneedstoberesolved,wecanaccessthecalculatedvaluefromthearrayresult.Ifitcontainsastring,wetrytoconvertittoanumber.

functionresolveVariable(location:Variable):number|Failure[]{

const{column,row}=location;

constvalue=result[column][row];

if(typeofvalue==="string"){

constnum=parse(parseConstant,value);

Page 936: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

if(num===undefined){

return[newFailure(FailureKind.TextNotANumber,location)];

}

returnnum.value;

}elseif(valueinstanceofeArray){

return[newFailure(FailureKind.FailedDependentRow,location)];

}else{

returnvalue;

}

}

}

Wehavealreadywrittenaparserthatcanparseaconstant,sowecanreuseithere.Ifitcontainsanarrayoferrors,wereturnanewerror,whichsaysthatareferencedfieldcontainsanerror.Otherwise,thefieldcontainsanumberandwecansimplyreturnthat.

Page 937: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingtheFluxarchitectureInReact,everyclasscomponentcanhaveastate.Maintainingastateisasideeffectandnotpure,sowewillnotusethatinthisapplication.Instead,wewilluseStatelessFunctionalComponents,whicharepure.Westillneedtomaintainthestateoftheapplication.WewillusetheFluxarchitecturetodothat.WithFlux,youneedtowriteasmallpieceofnon-pure,buttheotherpartsoftheapplicationcanbewrittenpure.Thearchitecturecanbedividedintotheseparts:

Store:ContainsthestateoftheapplicationView:ReactcomponentsthatrenderthestatetoHTMLAction:Afunctionthatcanmodifythestate(example:renamethespreadsheet)Dispatcher:Ahubmodifiesthestatebyexecutinganaction

SeveralimplementationsofFluxexist.Wewillbuildourown,sothatwecanunderstandtheideasbetterandwecancreateanimplementationthatcanbeproperlytypedusingTypeScript.

Wewillimplementthesepartsinthefollowingsections.

Page 938: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DefiningthestateInlib/model/state.ts,wecandefineaninterfacethatcontainsthestateoftheapplication.Thestateshouldcontainthisinformation:

ActivespreadsheetCalculatedresultsofallexpressionsSelectedcolumnandrowifapopupisopenedContentofthetextboxofthepopupWhetherornotthetextboxofthepopupcontainsasyntaxerror

Thisyieldsthefollowingdeclaration:

import{Sheet,Result}from"./sheet";

exportinterfaceState{

sheet:Sheet;

result:Result;

selectedColumn:number;

selectedRow:number;

popupInput:string;

popupSyntaxError:boolean;

}

Ifthepopupisnotshown,wewillsetselectedColumnandselectedRowtoundefined.Otherwise,thesepropertieswillcontainthecolumnandrowoftheselectedfield.

constemptyRow=["",""];

constemptyGrid=[

emptyRow,

emptyRow

]

exportconstemptySheet=newSheet("Untitled",emptyGrid)

exportconstempty:State={

sheet:emptySheet,

result:emptyGrid,

selectedColumn:undefined,

selectedRow:undefined,

popupInput:"",

popupSyntaxError:false

}

Weshouldalsoconstructthestateoftheapplicationwhenitstarts.Itshouldcontainanemptysheetandthepopupshouldnotbeopen.

Page 939: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthestoreanddispatcherWewillcreatethestoreanddispatcherinlib/model/store.ts.Thedispatchershouldtakeanactionandexecuteit.Wefirstdefineanactionasafunctionthatmodifiesastate.Sincewecannotassigntothestate,asthatisnotpure,anactionshouldnotadjusttheoldstate,butcreateanewstateobjectwithacertainmodification.

exporttypeAction<T>=(state:T)=>T;

Thedispatchershouldacceptsuchaction.Wedefinethedispatcherasafunctionwithanactionasanargument.

exporttypeDispatch<T>=(action:Action<T>)=>void;

Wecannowcreatethestore.Thestoreshouldfireacallbackwhenthestatechanges.

exportfunctioncreateStore<U>(state:U,onChange:(newState:U)=>void){

constdispatch:Dispatch<U>=action=>{

state=action(state);

onChange(state);

}

returndispatch;

}

Thestorealsoneedsaninitialstate.WeaddthesetwoasargumentstothecreateStorefunction.Thefunctionwillreturnthedispatcher.

Page 940: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingactionsAnactionshouldmodifythestate.Todothat,wewillfirstcreatethreehelperfunctions.Onetomodifyapartofanobject,onetomodifyapartofanarray,andonetoeasilycreateanewarray.

WeusethesameupdatefunctionaswedidinChapter3,Note-TakingAppwithaServer.Weaddthisfunctiontolib/model/utils.ts.

exportfunctionupdate<UextendsV,V>(old:U,changes:V):U{

constresult=Object.create(Object.getPrototypeOf(old));

for(constkeyofObject.keys(old)){

result[key]=(<any>old)[key];

}

for(constkeyofObject.keys(changes)){

result[key]=(<any>changes)[key];

}

returnresult;

}

Wealsocreateafunctionthatchangestheelementatacertainindexofanarray.Theotherelementswillremainatthesamelocation.Wewillusethisfunctiontochangethecontentofafieldofthespreadsheetlateron.

exportfunctionupdateArray<U>(array:U[],index:number,item:U){

return[...array.slice(0,index),item,...array.slice(index+1)];

}

WedefineafunctionrangeMap,whichcreatesanarray.Thecallbackargumentisusedtocreateeachelementofthearray.

exportfunctionrangeMap<U>(start:number,end:number,callback:(index:

number)=>U):U[]{

constresult:U[]=[];

for(leti=start;i<end;i++){

result[i]=callback(i);

}

returnresult;

}

Tip

ThefunctionsupdateandrangeMaparenotpure,sincethefunctionscontainseveralassignments.Sometimesitisnotpossibleorveryhardtowriteafunctionpure.However,thesefunctionskeepthesideeffectslocalandotherfunctionswillnotperceivethatthefunctionispure.

Page 941: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingacolumnorarowInlib/model/action.ts,wewillcreatetheactionsforourapplication.Firstwemustimportthetypesandfunctionthatwehavewrittenbefore.

import{State}from"./state";

import{calculateSheet,Field,rows,fieldToString,parseField}from

"./sheet";

import{update,updateArray,rangeMap}from"./utils";

Nowwecancreateanactionthatcalculatesallexpressions.Wewillnotexportthisaction,butwewilluseitinotheractions.

constmodifyResult=(state:State)=>

update(state,{

result:calculateSheet(state.sheet)

});

Withthisdefinition,modifyResultisafunctionthattakesastateandreturnsanupdatedstatewithamodifiedresultproperty.ThisconformstotheActiontypethatwedefinedearlier.

Wecanusethisfunctiontocreatetheactionsthataddaroworcolumn.Ifanewrowneedstobeadded,everycolumnshouldgetanextrafieldattheend.Thisfieldshouldbeempty;itshouldcontaintheemptystring.Afterwards,weneedtoupdatetheresultpropertyofthestate.WewillusethemodifyResultfunctionforthat.

exportconstaddRow=(state:State)=>

modifyResult(update(state,{

sheet:update(state.sheet,{

grid:state.sheet.grid.map(column=>[...column,""])

})

}));

Toaddanewcolumn,wemustaddanewarraywithemptystrings.WewilluserangeMaptocreatesuchanarray.Wecanuserowstogettheamountofrows,andthusthelengthofthenewarray.

exportconstaddColumn=(state:State)=>

modifyResult(update(state,{

sheet:update(state.sheet,{

grid:[

...state.sheet.grid,

rangeMap(0,rows(state.sheet),()=>"")

]

})

}));

Lateron,theseactionscanbetriggeredbydispatch(addRow)ordispatch(addColumn).Wewillseethatinactionwhenwecreatetheview.

Page 942: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChangingthetitleAddingaroworacolumnisanactionthatdoesnothavearguments.Changingthetitledoesrequireanargument,namelythenewtitle.Sincethedefinitionofanactiondoesnotallowextraarguments,wecannotwriteitasafunctionthatrequiresthenewtitleandthecurrentstate.Instead,wecancreateafunctionthattakesthenewtitle,andthenreturnsafunctionthatrequiresthecurrentstate.Thatwillgivethisdefinition:

exportconstsetTitle=(title:string)=>(state:State)=>

update(state,{

sheet:update(state.sheet,{title})

});

Thisactioncanbefiredbyrunningdispatch(setTitle("Untitled")).Ifyouforgettheargument,orspecifyawrongargument,TypeScriptwillgiveanerror.OtherimplementationsofFluxmakeithardtotypesuchactions.

Page 943: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ShowingtheinputpopupWeneedtocreateseveralactionsforthepopup:

OpenthepopupCloseitToggleit(closeifitisalreadyopen,closeitotherwise)ChangetheinputofthetextboxSavethenewvalue

Toopenthepopup,weneedthecolumnandtherowofthefieldandsavetheminthestate.Wesettheinputofthepopuptothecontentofthatfield,eitherastringortheexpressionconvertedtoastring.Whenthepopupisopened,itcannothaveanysyntaxerrorssowesetthatpropertytofalse.

exportconstpopupOpen=(selectedColumn:number,selectedRow:number)=>(state:

State)=>

update(state,{

selectedColumn,

selectedRow,

popupInput:fieldToString(state.sheet.grid[selectedColumn][selectedRow]),

popupSyntaxError:false

});

Thepopupcanbeclosedbysettingthecolumnandrowtoundefined.

exportconstpopupClose=(state:State)=>

update(state,{

selectedColumn:undefined,

selectedRow:undefined,

popupInput:""

});

Totogglethepopup,wecheckwhetheritopenedinthespecifiedlocation,andcloseitoropenitafterwards.

exportconstpopupToggle=(column:number,row:number)=>(state:State)=>

(column===state.selectedColumn&&row===state.selectedRow)

?popupClose(state):popupOpen(column,row)(state);

Wecanupdatethecontentoftheinputbox:

exportconstpopupChangeInput=(popupInput:string)=>(state:State)=>

update(state,{

popupInput

});

Finally,wecancreateanactionthatsavestheinputinthepopupandclosesit.However,whenthepopupcontainsasyntaxerror,wewillnotclosethepopup,butwewilltelltheuserthattheinputcontainsanerror.Insuchacase,parseFieldwillreturnundefined.Otherwise,wechangethefieldthatisselectedandrecalculatethewholespreadsheet.

Page 944: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

exportconstpopupSave=(state:State)=>{

constinput=state.popupInput;

constvalue=parseField(input);

if(value===undefined){

returnupdate(state,{

popupSyntaxError:true

});

}

returnmodifyResult(update(state,{

sheet:update(state.sheet,{

grid:updateArray(state.sheet.grid,state.selectedColumn,

updateArray(state.sheet.grid[state.selectedColumn],state.selectedRow,

value)

)

}),

selectedColumn:undefined,

selectedRow:undefined,

popupInput:""

}));

};

Theseareallactionsofourapplications.

Page 945: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingactionsSinceactionsarepurefunctions,wecaneasilytestthem.Theycanbetestedwithoutthestore,dispatcher,andview.Todemonstratethis,wewillwritetestsforaddColumn,addRowandsetTitle.WestartwithimportingAVA,thesefunctionsandsomehelperfunctions.

import*astestfrom"ava";

import{empty}from"../model/state";

import{addColumn,addRow,setTitle}from"../model/action";

import{columns,rows}from"../model/sheet";

WewillwriteatestforaddColumn.Wevalidatethattheamountofcolumnsisincreasedbyoneandthattheamountofrowshasnotbeenchanged.

test("addColumn",t=>{

conststate=addColumn(empty);

t.is(columns(state.sheet),columns(empty.sheet)+1);

t.is(rows(state.sheet),rows(empty.sheet));

});

WewriteatestforaddRowtoo.Thistime,wevalidatethattheamountofcolumnsstayedthesamebuttheamountofrowsincreased.

test("addRow",t=>{

conststate=addRow(empty);

t.is(columns(state.sheet),columns(empty.sheet));

t.is(rows(state.sheet),rows(empty.sheet)+1);

});

ForsetTitle,wecheckthatthetitlehasindeedbeenchangedandthatthegridhasnotchanged.

test("setTitle",t=>{

conststate=setTitle("foo")(empty);

t.is(state.sheet.title,"foo");

t.is(state.sheet.grid,empty.sheet.grid);

});

Tip

Whenyougetabugreport,trytocreateaunittestthatdemonstratesthaterror.Whenyouhavefixedthebug,youcaneasilyvalidateitbyrunningthetestsandyoupreventthebugfromreturninginthefeature.

Page 946: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WritingtheviewTheapplicationwillshowaninputboxatthetopofthescreen,whichisusedtotypethetitleofthespreadsheet.Belowthetitle,atableisshownwhichcontainsallfieldsofthespreadsheet.Whentheuserclicksonafield,apopupiscreatedwhichallowstheusertochangethecontentofthatfield.Ifthefieldcontainserrors,theseerrorsareshowninthepopup:

WewilluseReacttocreatetheviewofourapplication.WithStatelessFunctionalComponents,wecanwritepurefunctionsthatrenderthestate.

Page 947: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RenderingthegridInlib/client/sheet.tsx,wewillimportReactandfunctionsandtypesthatwecreatedbefore:

import*asReactfrom"react";

import{Dispatch}from"../model/store";

import{Expression,expressionToString,failureText}from

"../model/expression"

import{State}from"../model/state";

import{Sheet,Field,Result,ResultField,columns,rows,parseField,

fieldToString}from"../model/sheet";

import{update,rangeMap}from"../model/utils";

import*asactionfrom"../model/action";

WewillrenderthespreadsheetinRenderSheet.Thatfunctionrequiresthestateandthedispatcher.

exportfunctionRenderSheet({state,dispatch}:{state:State,dispatch:

Dispatch<State>}){

const{sheet,result}=state;

constcolumnCount=columns(sheet);

constrowCount=rows(sheet);

Atthetopofthescreen,weshowtheinputbox.Whentheuserchangesthetitle,weadjustthestatetoitwiththesetTitleaction.

return(

<divclassName="sheet">

<inputclassName="sheet-title"value={sheet.title}

onChange={e=>dispatch(action.setTitle((e.targetas

HTMLInputElement).value))}/>

Weshowthetablebelowthetitle.Inthistable,weshowthecalculatedvaluesofallfields.Wealsoshowtwobuttonstoaddanewroworcolumn.Thesebuttonsdispatchtheactionsthatwedefinedearlier.

<table>

<tbody>

<tr>

<th></th>

{rangeMap(0,columnCount,index=><thkey={index}>{

index}</th>)}

<throwSpan={rowCount+1}className="sheet-add-

column">

<ahref="javascript:;"

onClick={()=>dispatch(action.addColumn)}>Add

column</a>

</th>

</tr>

{rangeMap(0,rowCount,renderRow)}

<tr><thcolSpan={columnCount+2}>

<ahref="javascript:;"

Page 948: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

onClick={()=>dispatch(action.addRow)}>Addrow</a>

</th></tr>

</tbody>

</table>

</div>

);

WerenderarowintherenderRowfunction.WeuserangeMaptocallthisfunction,andtocallrenderColumn.Reactrequiresthatweusethekeypropertyinaloop.Weassigntherowandcolumnindextoit,sincethesewillbeunique.

functionrenderRow(row:number){

return(

<trkey={row}>

<th>{row}</th>

{rangeMap(0,columnCount,renderColumn)}

</tr>

);

functionrenderColumn(column:number){

return(

<RenderFieldkey={column}column={column}row={row}state={state}

dispatch={dispatch}/>

);

}

}

}

Reactcomponentsshouldstartwithacapitalletter.Normalfunctionsshouldbenamedwithalowerletterasaconvention,butforcomponentswehavetobreakthatrule.

Page 949: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RenderingafieldTorenderafield,wewillfirstquerythecontentofthefieldandcheckwhetherthepopupisopenonthisfield.

functionRenderField({column,row,state,dispatch}:{column:number,row:

number,state:State,dispatch:Dispatch<State>}){

constfield=state.sheet.grid[column][row];

constresult=state.result[column][row];

constopen=state.selectedColumn===column

&&state.selectedRow===row;

Nowwecheckwhetherthefieldcontainstextoranexpression.Incaseofanexpression,itcaneitherbeasuccessfulcalculationorafailedone.Ifitfailed,wewillshowtheamountoferrors.Inthepopup,theusercanreadallerrors.Wegenerateaclassnamebasedonthis,andonwhetherthepopupisopenedinthisfield.

lettext:string;

letclassName:string;

if(typeofresult==="string"){

text=result;

className="field-string";

}elseif(typeofresult==="number"){

text=result.toString();

className="field-value";

}else{

text=result.length===1?"1error":result.length+"errors";

className="field-error";

}

className+="field";

if(open){

className+="field-open";

}

Withthesevariables,wecanrenderthefield.Ifwemustshowthepopup,wewilldothatwithRenderPopup.

return(

<tdclassName={className}>

<spanonClick={()=>dispatch(action.popupToggle(column,row))}>

{text}

</span>

{open?

<RenderPopup

field={field}

content={result}

syntaxError={state.popupSyntaxError}

input={state.popupInput}

dispatch={dispatch}/>

:undefined

}

</td>

Page 950: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

);

}

Wedefinethatfunctioninthenextsection.Weattachaneventlistenertothefieldwhichwillopenorclosethefieldwhentheuserclicksonit.

Page 951: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ShowingthepopupWewillshowthepopupinRenderPopup.Thepopupcontainsaninputbox,asave,andcancelbutton:

Ifthefieldcontainsanerror,weshowitbelowthetwobuttons:

Forerrorsotherthansyntaxerrors,weshowthelocationwhereithappened:

Wewillfirststoreallerrorsinavariable.Incaseofasyntaxerror,wecannotgivedetails.Forothererrors,weshowadescriptionandthelocationoftheerror.

functionRenderPopup({field,content,syntaxError,input,dispatch}:{field:

Field,content:ResultField,syntaxError:boolean,input:string,dispatch:

Dispatch<State>}){

leterrors:JSX.Element|JSX.Element[];

if(syntaxError){

errors=<divclassName="failure">

Couldnotparsethisexpression.

</div>;

}elseif(contentinstanceofArray){

errors=content.map((failure,index)=><divclassName="failure"key=

{index.toString()}>

<spanclassName="failure-text">{failureText(failure)}</span>

<spanclassName="failure-source">{expressionToString(failure.location)}

</span>

</div>);

}

Nowwecanbuildthefullview.Weattacheventlistenerstotheinputbox,save,andclosebutton.Wealsowraptheinputboxinaform,suchthattheusercanpressEnter(insteadof

Page 952: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

clickingSave)toacceptthechanges.

return(

<divclassName="field-popup">

<formonSubmit={(e)=>{e.preventDefault();dispatch(action.popupSave);}}>

<inputvalue={input}autoFocus

onChange={e=>dispatch(action.popupChangeInput((e.targetas

HTMLInputElement).value))}/>

</form>

<ahref="javascript:;"onClick={()=>dispatch(action.popupSave)}>Save</a>

<ahref="javascript:;"onClick={()=>

dispatch(action.popupClose)}>Cancel</a>

<br/>

{errors}

</div>

);

}

Page 953: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingstylesInstatic/style.css,wewilladdsomemorestyles.Wewillmakethetextoftheinputboxforthetitlebigger.

.sheet-title{

font-size:24pt;

margin:0010px;

border:1pxsolid#ccc;

width:200px;

}

Wewilladdabordertothetableandstylethebuttontoaddacolumn.

.sheet>table,.sheettr,.sheetth,.sheettd{

border:1pxsolid#ccc;

border-collapse:collapse;

}

.sheet-add-column>a{

width:70px;

display:block;

}

Wewillstylethefieldssotheyshowtheirvalueproperlyandcancontainapopup.

.field{

position:relative;

}

.field>span{

display:block;

min-width:42px;

font-size:10pt;

height:18px;

padding:3px;

}

.field-value>span{

font-family:Cambria,Cochin,Georgia,Times,TimesNewRoman,serif;

text-align:right;

}

.field-error>span{

color:#aa2222;

}

.field-open{

background-color:#eee;

}

Weaddsomestylestothepopup:

.field-popup{

position:absolute;

left:0px;

top:20px;

z-index:10;

background-color:#eee;

Page 954: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

border-bottom:4pxsolid#5a8bb8;

border-right:1pxsolid#ddd;

padding:8px;

width:300px;

}

.field-popup>input{

margin-right:10px;

}

.field-popup>a{

margin-left:10px;

}

Finally,wechangethelooksoferrormessagesinthepopup.

.failure-text{

font-style:italic;

}

.failure-source{

margin-left:10px;

color:#555;

font-family:Cambria,Cochin,Georgia,Times,TimesNewRoman,serif;

}

Page 955: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GluingeverythingtogetherInlib/client/index.tsx,wewillcombineallpartsofourapplication.Wewillcreateacomponentthatcontainsthestateandrenderstheview.Whenthestateisupdatedinthestore,wewillpropagatethattothiscomponentandrendertheviewagain.

import*asReactfrom"react";

import{render}from"react-dom";

import{createStore,Dispatch}from"../model/store";

import{State,empty}from"../model/state";

import{RenderSheet}from"./sheet";

classAppextendsReact.Component<{},State>{

dispatch:Dispatch<State>;

state=empty;

constructor(props:{}){

super(props);

this.dispatch=createStore(this.state,state=>this.setState(state));

}

render(){

return(

<divclassName="sheet">

<RenderSheet

state={this.state}

dispatch={this.dispatch}/>

</div>

);

}

}

Finally,wecanrenderthiscomponentintheHTMLfile.

render(<App/>,document.getElementById("wrapper"));

Wecanviewtheresultbyrunninggulpcompileandopeningstatic/index.htmlinyourbrowser.

Page 956: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AdvantagesofFluxInthissectionyoucanfindsomeoftheadvantagesofusingFlux,thearchitecturethatweusedinthischapter.

Fluxisbasedontheunidirectionalflowofdata.Angularsupportstwowaybindings,whichallowdatatoflowintwodirections.Withthisdataflow,alotofpropertiesmightgetchangedafterasinglechangeismade.Thiscanleadtounpredictablebehaviorinbigapplications.FlowandReactdonothavesuchbindings,butinsteadthereisacleanflowofdata(store|view|action|dispatch|store).

ThepartsofFluxarenotstrictlyboundtoeachother.Thismakesiteasytotestspecificpartsoftheapplicationwithunittests.Wealreadysawthattheactionsdonotdependontheview.

Page 957: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Goingcross-platformSincethepartsofFluxarenotbound,wecan,relatively,replacetheHTMLviewsoftheapplicationwithviewsofadifferentplatform.Theuserinterfacedoesnotstorethestateoftheapplication,butitismanagedinthestore.TheotherpartsneednomodificationwhentheHTMLviewsarereplaced.Thiswaywecanporttheapplicationtoadifferentplatformandgocross-platform.

Page 958: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryWehavebuiltaspreadsheetapplicationwithfunctionalprogramming,React,andFluxinthischapter.Wehavediscoveredthelimitationsoffunctionalprogrammingandlearnedhowwecantakeadvantageofit.Wehavewrittenautomatedunittestsforpartsofthecodethatwehavewritten.Wealsosawhowwecantraversedatastructuresandwriteaparserwithfunctionalprogramming.WiththeFluxarchitecture,welearnthowwecanwritethebiggestpartoftheapplicationwithpurefunctions.

Inthenextchapter,wewillseemoreoffunctionalprogramming.WewillrebuildPac-ManwiththeHTML5canvas.

Page 959: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter8.PacManinHTML5Inthischapter,wewillrecreatePacManwiththeHTML5canvas.Justlikethepreviouschapter,wewillbeusingfunctionalprogramming.WiththeHTML5canvasandJavaScript,youcanplaygamesinthebrowser.

PacManisaclassicgamewheretheplayer(PacMan,theyellowcircle)musteatallofthedots.TheghostsaretheenemiesofPacMan:whenyougetcaughtbyaghost,youlose.Ifyoueatallofthedotswithoutbeingcaughtbyaghost,youwinthegame.

Drawingonacanvasis,justlikemodifyingtheHTMLelementsofapage,asideeffectandthusnotpure.Sincewewillbeusingfunctionalprogramming,wewillcreatesomeabstractionaroundit,similartowhatReactdoes.Wewillbuildasmallnon-pureframeworksowecanusethattobuildtherestofthegamewithpurefunctions.WewillalsousestrictNullChecksinthischapter.Thecompilerwillcheckwhichvaluescanbeundefinedornull.

Wewillbuildthegameinthesesteps:

SettinguptheprojectUsingtheHTML5canvasDesigningtheframeworkDrawingonthecanvasAddingutilityfunctionsCreatingthemodelsDrawingtheviewHandlingeventsCreatingthetimehandlerRunningthegameAddingamenu

Page 960: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SettinguptheprojectTheprojectstructurewillbesimilartothepreviousprojects.Inlib,wewillplaceoursources.Weseparatethefilesfortheframeworkandthegameinlib/frameworkandlib/game.Inlib/tsconfig.json,weconfigureTypeScript:

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"strictNullChecks":true

}

}

Intherootdirectory,wesetupgulpingulpfile.js:

vargulp=require("gulp");

varts=require("gulp-typescript");

varsmall=require("small").gulp;

vartsProject=ts.createProject("lib/tsconfig.json");

gulp.task("compile",function(){

returngulp.src("lib/**/*.ts")

.pipe(ts(tsProject))

.pipe(small("game/index.js",{outputFileName:{standalone:"scripts.js"

}}))

.pipe(gulp.dest("static/scripts/"));

});

gulp.task("default",["compile"]);

WecaninstallourdependencieswithNPM.

npminit-y

npminstallgulpgulp-typescriptsmall--save-dev

Finally,wecreateasimpleHTMLfileinstatic/index.html.

<!DOCTYPEHTML>

<html>

<head>

<title>PacMan</title>

</head>

<bodystyle="background-color:black;">

<canvasid="game"width="800"height="600"></canvas>

<scriptsrc="scripts/scripts.js"></script>

</body>

</html>

Beforewestartwritingtheframework,wewillhaveaquicklookathowtheHTML5canvasworks.

Page 961: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingtheHTML5canvasTheHTML5canvasisanHTMLelement,justlike<div>.However,thecanvasdoesnotcontainotherHTMLelements,butitcancontainadrawinggeneratedbyJavaScriptcode.Inlib/game/index.tswewillquicklyexperimentwithit.

Wecangetareferencetothecanvasusingdocument.getElementByIdthesamewaywegotareferencetoa<div>element:

constcanvas=<HTMLCanvasElement>document.getElementById("game");

Wecannotdirectlydrawonthecanvas;wehavetogetarenderingcontextfirst.Currently,twokindsofrenderingcontextsexist:atwodimensionalcontextandawebglcontext,usedfor3Drendering.Thewebglcontextisalothardertouse.Luckily,PacManis2D,sowecanusethe2Dcontext:

constcontext=canvas.getContext("2d");

Inaneditorwithcompletions,youcancheckwhichfunctionsexistonthecontext.Forinstance,youcanusecontext.fillRect(10,10,100,100)todrawafilledrectanglefrom10,10to110,110.Thex-axisstartsattheleftsideandgoestotheright,andthey-axisstartsatthetopofthecanvasandgoesdown.

Beforeyoucandrawanythingonthecanvas,youmustsetthedrawingcolor.Thecanvasdistinguishestwodifferentcolorsettings:thefillcolorandthestrokecolor.Thefillcolorisusedtopaintafilledshape.Thestrokecolorisusedtodrawashapethatonlyconsistsofanoutline.

Wecansetthesecolorsusingcontext.fillStyleandcontext.strokeStyle:

context.fillStyle="#ff0000";

context.strokeStyle="#0000ff";

Wecanalsosettheweightofalinewithasimilarproperty.

context.lineWidth=5;

Wecandrawrectangleswiththesestyles.

context.fillRect(10,10,100,100);

context.strokeRect(20,20,100,100);

Thisresultsinthefollowingimage:

Page 962: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management
Page 963: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SavingandrestoringthestateThecontextalsohasthefunctionssave()andrestore().Withthesefunctions,youcanrestorethecurrentdrawstyles,suchasfillStyle,andlineWidth.restore()resetsthestatetothelasttimethatsave()wascalled,basedonaLIFOstack(LastIn,FirstOut).

Inthefollowingexample,therestoreonposition3resetsthestatetothestatesavedonposition2,andrestoreonposition4resetsittoposition1:

context.save();//1

context.fillStyle="#ff0000";

context.save();//2

context.strokeStyle="#0000ff";

context.restore();//3

context.restore();//4

Wewillusethesefunctionsintheframeworkastheycaneasilybeusedwithrecursion.

Page 964: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DesigningtheframeworkWewilldesigntheframeworkbasedonfunctionalprogramming.Theframeworkwilldoallnon-purework,sothattherestoftheapplicationcanbebuiltwithpurefunctions(exceptforMath.random).

Tip

Strictlyspeaking,Math.randomisnotapurefunction.GiventhatMath.random()isnotalwaysequaltoMath.random(),thatfunctionwillupdatesomeinternalstate.Inpurefunctionallanguages,suchafunctioncanstillexist.Thatfunctiontakesastateandreturnsarandomnumberandanewstate.Sinceeverycalltorandomwillgetadifferentstate,itcanreturndifferentrandomvalues.

Agameconsistsofaneventloop.TheamountofiterationsthatthisloopdoespersecondiscalledFPSorframespersecond.Everystepoftheloop,thegamestateneedstobeupdated.Forinstance,enemiesandtheplayercanmove,andtheplayercaneatdotsinPacMan.Attheendofeachstep,thegamestatemustberedrawn.

Thegamemustalsohandleuserinput.Whentheuserpressestheleftbutton,theplayershouldstartmovingtotheleft.

Wewillsplittheeventloopintothefollowingcomponents:

Theview,whichwilldrawthegameeverystepAtimehandler,whichwillbecalledonceineverystepAneventhandler,whichwillbecalledforeveryeventthatoccurs

Withfunctionalprogrammingitcanoftenbeusefultothinkaboutthetypesoffunctionsbeforeyouwritethem.Wewilltakeaquicklookatthetypesofthesethreecomponents.ImaginethestateisstoredinsomeinterfaceState.Theviewwilltransformthisstateintoapicture.Theviewmightneedthewidthandheightofthecanvas,soweaddtheseasarguments.WewillcreatethedefinitionofaPicturelateron:

functiondraw(state:State,width:number,height:number):Picture

Thetimehandlershouldtransformthestateintoanewstate.Itshouldnothaveanyotherarguments:

functiontimeHandler(state:State):State

Theeventhandleralsotransformsthestate,butitcanuseanextraargument,whichcontainstheeventthathasoccurred:

functioneventHandler(state:State,event:Event):State

Inthenextsections,wewillcreateaframeworkthatmanagesthesethreecomponents.

Page 965: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingpicturesWewillstartbycreatingdatatypesforpictures.Someexamplesofapictureareacircle,aline,text,oracombinationofthose.Suchpicturescanalsobescaled,repositioned(translated),orrotated.Anemptypictureisalsoapicture.

Wedefineapictureastheunionofthesedifferentkinds:

exporttypePicture

=Empty

|Rectangle

|RectangleOutline

|Circle

|CircleOutline

|Line

|Text

|Color

|Translate

|Rotate

|Scale

|Pictures;

Westartbycreatingsomebasictypes.TheEmptypicturecanbedefinedasfollows:

exportclassEmpty{

__emptyBrand:void;

}

Thisclassdoesnotneedanyproperties.However,ifyoudonotaddanypropertiestoaclass,valuesofeverytypewillbeassignabletoit.ThisisbecauseTypeScripthasastructuraltypesystem,andforinstanceastringhas,attheleast,allpropertiesofanemptyclass(thatis,noproperties).Forinstance,astringoranumberisassignabletothatclass.Topreventthat,weaddabrandtotheclass.Abrandisapropertythatdoesnotexistatruntime,butisusedtopreventissueswithstructuraltyping.

Forrectanglesandcircles,wecreatedifferenttypes.Oneisfilled,onehasonlytheoutline.Forsuchoutlines,wecansetthethickness:

exportclassRectangle{

__rectangleBrand:void;

constructor(

publicx=0,

publicy=0,

publicwidth=1,

publicheight=width

){}

}

exportclassRectangleOutline{

__rectangleOutlineBrand:void;

constructor(

Page 966: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

publicx=0,

publicy=0,

publicwidth=1,

publicheight=width,

publicthickness=1

){}

}

Ifwedonotaddabrandtothesedefinitions,aRectanglewillbeassignabletoaRectangleOutline.Thesebrandsarealsonecessarytodifferentiatearectangleandacircle:

exportclassCircle{

__circleBrand:void;

constructor(

publicx=0,

publicy=0,

publicwidth=1,

publicheight=width

){}

}

exportclassCircleOutline{

__circleOutlineBrand:void;

constructor(

publicx=0,

publicy=0,

publicwidth=1,

publicheight=width,

publicthickness=1

){}

}

Wedefinealineasalistofpointsandathickness:

exporttypePoint=[number,number];

exporttypePath=Point[];

exportclassLine{

__lineBrand:void;

constructor(

publicpath:Path,

publicthickness:number

){}

}

Next,wedefinethetypefortext:

exportclassText{

__textBrand:void;

constructor(

publictext:string,

publicfont:string

){}

Page 967: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

Page 968: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WrappingotherpicturesWecanwrapotherpicturesandcreatenewones.Forinstance,wewillchangethecolorofapicturewithColor.WiththisdefinitionwecanwritenewColor("#ff0000",newCircle(0,0,2,2))togetaredcircle:

exportclassColor{

__colorBrand:void;

constructor(

publiccolor:string,

publicpicture:Picture

){}

}

Wecouldalsorepositionapicture.Thisisusuallycalledtranslating.newTranslate(100,100,newCircle(0,0,2,2))drawsacirclearound(100,100)insteadof(0,0):

exportclassTranslate{

__translateBrand:void;

constructor(

publicx:number,

publicy:number,

publicpicture:Picture

){}

}

Asthenamesuggests,Rotaterotatessomeotherpicture:

exportclassRotate{

__rotateBrand:void;

constructor(

publicangle:number,

publicpicture:Picture

){}

}

WecanresizeapicturewithScale.newScale(5,5,newCircle(0,0,2,2))woulddrawacircleof10x10insteadof2x2:

exportclassScale{

__scaleBrand:void;

constructor(

publicx:number,

publicy:number,

publicpicture:Picture

){}

}

Thelastclassprovidesawaytoshowmultiplepicturesasonepicture:

Page 969: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

exportclassPictures{

__picturesBrand:void;

constructor(

publicpictures:Picture[]

){}

}

Inlib/framework/draw.ts,wewilldrawthesepicturesonacanvas.Wewillimplementthatfunctionlater;wewillnowonlydefineitsheader:

import{Picture,Rectangle,RectangleOutline,Circle,CircleOutline,Line,

Text,Color,Translate,Rotate,Scale,Pictures,Path}from"./picture";

exportfunctiondrawPicture(context:CanvasRenderingContext2D,item:Picture){

}

Wehavenowdefinedallthedatatypesneededtodrawapicture.WewillcreateeventsbeforeweimplementthedrawPicturefunction.

Page 970: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingeventsTheapplicationcanacceptkeyboardevents.Wewilldistinguishbetweentwokindsofevent:akeypressandakeyrelease.Wewillnotaddmouseevents,butyoucanaddtheseyourself.Wedefinetheseeventsinlib/framework/event.ts.

Everykeyhasacertainkeycode,anumberthatidentifiesakey.Forinstance,theleftarrowkeyhascode37.Wewilladdthekeycodetotheeventclass:

exportconstenumKeyEventKind{

Press,

Release

}

exportclassKeyEvent{

constructor(

publickind:KeyEventKind,

publickeyCode:number

){}

}

Wedefinetheeventsourceasafunctionthatwillbeinvokedeverystep.Itwillreturnalistofeventsthatoccurredinthatstep.

exportfunctioncreateEventSource(element:HTMLElement){

letqueue:KeyEvent[]=[];

consthandleKeyEvent=(kind:KeyEventKind)=>(e:KeyboardEvent)=>{

e.preventDefault();

queue.push(newKeyEvent(

kind,

e.keyCode

));

};

constkeypress=handleKeyEvent(KeyEventKind.Press);

constkeyup=handleKeyEvent(KeyEventKind.Release);

element.addEventListener("keydown",keypress);

element.addEventListener("keyup",keyup);

functionevents(){

constresult=queue;

queue=[];

returnresult;

}

returnevents;

}

Wewillcallthisfunctionineverysteptocheckfornewevents.Inthenextsection,wewillpasstheseeventstotheeventhandler,whichwillupdatethegamestate.

Page 971: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BindingeverythingtogetherInlib/framework/game.ts,wewillbindthesecomponentstogether.Wewillcreateafunctionthatstartstheeventloopandupdatesthestateeverystep.Thefunctionhasthesearguments:

Thecanvaselementonwhichthegamewillbedrawn.Theeventelement.Eventsonthiselementwillbesenttotheeventhandler.Thisdoesnothavetobethesameelementasthecanvas.Anelementneedsfocustogetkeyboardevents.Sincethecanvasdoesnotalwayshavefocus,itcanbebettertolistenforeventsonthebodyelement,ifthereisonlyonegameonthewebpage.Theamountofframespersecond.Theinitialstateofthegame.Afunctionthatdrawsthestate.Thetimehandler.Theeventhandler.

Weregisterthetypeofthestateasagenericortypeargument.Usersofthisfunctioncanprovidetheirowntype.TypeScriptwillautomaticallyinferthistypebasedonthevalueofthestateargument:

import{Picture}from"./picture";

import{drawPicture}from"./draw";

import{createEventSource,KeyEvent}from"./event";

exportfunctiongame<UState>(

canvas:HTMLCanvasElement,

eventElement:HTMLElement,

fps:number,

state:UState,

drawState:(state:UState,width:number,height:number)=>Picture,

timeHandler:(state:UState)=>UState=x=>x,

eventHandler:(state:UState,event:KeyEvent)=>UState){

WithcreateEventSource,whichwehavewrittenbefore,wecangetaneventsourceforthespecifiedelement:

consteventSource=createEventSource(eventElement);

Tosetupdrawing,wemustacquiretherenderingcontext:

constcontext=canvas.getContext("2d")!;

ThefunctiongetContextmayreturnnullwhenthecontexttypeisnotsupported.Thetype2dissupportedinallbrowsersthatsupportacanvas,sowecansafelycastitwithanexclamationmark.Thiscastwillremovethenullabilityfromthetype.Wecreateaninterval,suchthatthestepfunctionwillbecalledmultipletimespersecond,basedonthefpsparameter:

setInterval(step,1000/fps);

Page 972: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WewillusethefunctionrequestAnimationFrametorendertheview.Thisfunctiontakesacallbackthatwillbecalledwhenthebrowserwantstoredrawthepage.Ifthebrowserdoesnotneedtoredraw,orithasnotimeforit,itwillnottrytoredrawit.Ifthedrawfunctionispure,thisdoesnotaffectthegame:

letdrawAnimationFrame=-1;

draw();

functionstep(){

letprevious=state;

for(consteventofeventSource()){

state=eventHandler(state,event);

}

state=timeHandler(state);

if(previous!==state&&drawAnimationFrame===-1){

drawAnimationFrame=requestAnimationFrame(draw);

}

}

Finally,wecreatethedrawfunction.Thisfunctionrendersthepictureinthecenterofthescreen.Acanvashasanx-axisthatgoestotheleftanday-axisthatgoesdown.Inmathematics,however,they-axisgoestothetop.Wewillchoosethelatterandflipthewholepicture.context.restore();willrestorethestatetothestateatcontext.save().Thetransformationsdonotinfluenceanydrawingsafterthedrawfunction,forinstanceinthenextstep:

functiondraw(){

drawAnimationFrame=-1;

const{width,height}=canvas;

context.clearRect(0,0,width,height);

context.save();

context.translate(Math.round(width/2),Math.round(height/2));

context.scale(1,-1);

drawPicture(context,drawState(state,width,height));

context.restore();

}

}

Wewillusethesaveandrestorefunctioninthenextsectiontoo.Wewillthendrawallkindsofpictureonthecanvas.

Page 973: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DrawingonthecanvasInlib/framework/draw.ts,wewillimplementthedrawPicturefunctionthatwecreatedbefore.Usinginstanceofwecancheckwhichkindofpicturewemustdraw.

Wewillinterpretthelocationofanobjectasthecenterofit.Thus,newRectangle(10,10,100,100)willdrawarectanglearound10,10.WecandrawtheoutlineofarectangleorthewholerectanglewithstrokeRectandfillRect:

import{Picture,Rectangle,RectangleOutline,Circle,CircleOutline,Line,

Text,Color,Translate,Rotate,Scale,Pictures,Path}from"./picture";

exportfunctiondrawPicture(context:CanvasRenderingContext2D,item:Picture){

context.save();

if(iteminstanceofRectangleOutline){

const{x,y,width,height,thickness}=item;

context.strokeRect(x-width/2,y-height/2,width,height);

}elseif(iteminstanceofRectangle){

const{x,y,width,height}=item;

context.fillRect(x-width/2,y-height/2,width,height);

Todrawacircle,weusethearcfunction.Thatfunctiondoesnotdrawthecircleitself,butonlyregistersitspath.Wecandrawthelineorfillitusingstrokeorfill.WemustwraparcwithbeginPathandclosePathtodothat:

}elseif(iteminstanceofCircleOutline||iteminstanceofCircle){

const{x,y,width,height}=item;

if(width!==height){

context.scale(1,height/width);

}

context.beginPath();

context.arc(x,y,width/2,0,Math.PI*2);

context.closePath();

if(iteminstanceofCircleOutline){

context.lineWidth=item.thickness;

context.stroke();

}else{

context.fill();

}

Foraline,wemustdosomethingsimilar.WithlineTo,wecandrawonesectionoftheline.Alinedoesnothavetobeclosed;itdoesnothavetoendatthelocationitstarted.Thus,wedonotcallclosePath:

}elseif(iteminstanceofLine){

const{path,thickness}=item;

context.lineWidth=thickness;

context.beginPath();

if(path.length===0)return;

const[head,...tail]=path;

const[headX,headY]=head;

context.moveTo(headX,headY);

Page 974: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

for(const[x,y]oftail){

context.lineTo(x,y);

}

context.stroke();

WithfillText,wecandrawtextonthecanvas.Wewillcenterthetext.Wemustalsoscalethetext,sincewehaveflippedthewholecanvasingame.ts.Ifyouforgetthis,thetextwouldbeupsidedown:

}elseif(iteminstanceofText){

const{text,font}=item;

context.scale(1,-1);

context.font=font;

context.textAlign="center";

context.textBaseline="middle";

context.fillText(text,0,0);

Wewilldrawpicturesthatcontainotherpictures,suchasColororPictures,withrecursion.ForColor,wecansimplysetthecoloronthecontext:

}elseif(iteminstanceofColor){

const{color,picture}=item;

context.fillStyle=color;

context.strokeStyle=color;

drawPicture(context,picture);

ForTranslate,Rotate,andScale,wecanusethetranslate,rotate,andscalefunctionsthatexistontherenderingcontext:

}elseif(iteminstanceofTranslate){

const{x,y,picture}=item;

context.translate(x,y);

drawPicture(context,picture);

}elseif(iteminstanceofRotate){

const{angle,picture}=item;

context.rotate(angle);

drawPicture(context,picture);

}elseif(iteminstanceofScale){

const{x,y,picture}=item;

context.scale(x,y);

drawPicture(context,picture);

ForPictures,wecanusealooptorenderallpictures:

}elseif(iteminstanceofPictures){

const{pictures}=item;

for(constpictureofpictures){

drawPicture(context,picture);

}

}

Finally,werestorethestateofthecontext:

context.restore();

Page 975: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

Wehavenowfinishedtheworkontheframework.Wewilldevelopthegameinthenextsection.

Page 976: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingutilityfunctionsWewillwriteseveralutilityfunctionsinlib/game/utils.ts.Withflatten,wewilltransformanarrayofarraysintoonearray.

exportfunctionflatten<U>(source:U[][]):U[]{

return(<U[]>[]).concat(...source);

}

Withupdate,wecanmodifysomepropertiesofanobject.Thisisthesamefunctionasinpreviouschapters.

exportfunctionupdate<UextendsV,V>(old:U,changes:V):U{

constresult=Object.create(Object.getPrototypeOf(old));

for(constkeyofObject.keys(old)){

result[key]=(<any>old)[key];

}

for(constkeyofObject.keys(changes)){

result[key]=(<any>changes)[key];

}

returnresult;

}

Next,wewillcreateafunctionforworkingwithMath.random.randomIntwillreturnarandomintegerinacertainrangeandchancehasachancetoreturntrue:

exportfunctionrandomInt(min:number,max:number){

returnmin+Math.floor(Math.round(

Math.random()*(max-min+1)

));

}

exportfunctionchance(x:number){

returnMath.random()<x;

}

WecancalculatethedifferencebetweentwopointswiththePythagoreantheorem:

exportfunctionsquare(x:number){

returnx*x;

}

exportfunctiondistance(x1:number,y1:number,x2:number,y2:number){

returnMath.sqrt(square(x1-x2)+square(y1-y2));

}

Finally,wewriteafunctionthatcheckswhetheranumberisaninteger:

exportfunctionisInt(x:number){

returnMath.abs(Math.round(x)-x)<0.001;

}

Duetoroundingerrors,wemustcheckthatthevalueisnearaninteger.

Page 977: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthemodelsInlib/game/model.ts,wewillcreatethemodelsforthegame.Thesemodelswillcontainthestateofthegame,suchasthelocationoftheenemies,walls,anddots.Thestatemustalsocontainthecurrentmovementoftheplayerandthedifficultylevel,asthegamewillhavemultipledifficulties.

Page 978: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingenumsWestartwithseveralenums.Wecanstorethedifficultywithsuchanenum:

exportenumDifficulty{

Easy,

Hard,

Extreme

}

Thevaluesofanenumareconvertedtonumbersduringcompilation.TypeScriptgivesthefirstelementzeroasthevalue,thenextitemone,andsoon.Inthisexample,Easyis0,Hardis1,andExtremeis2.However,youcanalsoprovideothervalues.Forsomeapplications,thiscanbeuseful.WewillusecustomvaluestodefineMovement.Thisenumcontainsthefourdirectionsinwhichtheplayercanmove.Incasetheuserdoesnotmove,weuseNone.Wegivethemembersavalue:

exportenumMovement{

None=0,

Left=1,

Right=-1,

Top=2,

Bottom=-2

}

Withthesevalues,wecaneasilycreateafunctionthatcheckswhethertwomovementsareintheoppositedirection:theirsumshouldequalzero:

exportfunctionisOppositeMovement(a:Movement,b:Movement){

returna+b===0;

}

Otherusefulpatternsthatareoftenusedarebitwisevalues.Anumberisstoredinacomputerasmultiplebits.Forinstance,00000011(binary)equals3(decimal).Youcancalculatethedecimalvalueofabinarynumberasfollows.Thefirstpositionfromtherighthasvalue1.Thenexthasvalue2,then4,8,andsoon.Summingthevaluesofthepositionswithaoneresultsinthedecimalvalue.

YoucanusethisbinaryrepresentationtostoremultipleBooleansinanumber.00000011wouldthenmeanthatthefirsttwovaluesaretrue,andtheothervaluesare0.Weuseanenumtodefinethenamesoftheseproperties.<<isthebitwiseshiftoperator.1<<xmeansthat00000001isshiftedxbitstotheleft.Forinstance,1<<4resultsin00010000:

exportenumSide{

Left=1<<0,

Right=1<<1,

Top=1<<2,

Bottom=1<<3,

LeftTop=1<<4,

RightTop=1<<5,

Page 979: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

LeftBottom=1<<6,

RightBottom=1<<7

}

Wecancombinemultiplevaluesusingthebitwiseoroperator,|.Abitoftheoutputis1ifatleastoneoftheinputbitsonthatpositionis1.Thus,Side.Left|Side.Rightequals00000011.Wecancheckwhethersomebitistruewiththebitwiseandoperator,&.Abitoftheoutputofthisoperatoris1ifbothinputbitsonthatpositionare1.Forinstance,00000011andSide.Rightresultsin00000010.Thisisnotzero,sotheBooleanvalueofSide.Rightinthatnumberistrue.

Wewillusethislaterontodrawtheedgesofthewalls.Asyoucanseeinthefollowingscreenshot,theedgesofallwallsaredrawn.

Page 980: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

StoringthelevelNow,wewilldefineamodelthatcanstorethestateofalevel.Alevelcontainsseveralobjectsthatareplacedinacertainlocation:

exportinterfaceObject{

x:number;

y:number;

}

Anenemyshouldalsocontainthelocationatwhichitistargeted.Anenemymightnotalwaysknowwheretheplayeris,soitcannotalwaysruntowardtheplayer:

exportinterfaceEnemyextendsObject{

toX:number;

toY:number;

}

Forawall,westorethesidesonwhichwallsexist.Theseneighborsareusedtodrawthewalls:

exportinterfaceWallextendsObject{

neighbours:Side;

}

Wecanstoretheseobjectsinthelevel.Wealsostorethesizeofthegrid,thecurrentmovementandthemovementbasedonthekeyboardinputinthelevel:

exportinterfaceLevel{

walls:Wall[];

dots:Object[];

enemies:Enemy[];

player:Object;

width:number;

height:number;

inputMovement:Movement;

currentMovement:Movement;

difficulty:Difficulty;

}

Page 981: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthedefaultlevelWewillwriteafunctionthatcanparsealevel,basedonastring.Thisallowsustocreatethelevelasfollows:

constdefaultLevel=parseLevel([

"WWWWWWWWWWWWWWW",

"W....E........W",

"W.WWWWW.WWWWW.W",

"W.W...W.W...W.W",

"WE..W.....W...W",

"W.W...W.W...W.W",

"W.WWWWW.WWWWW.W",

"W.............W",

"WWWW.WWWW.WWWW",

"W....WW....W",

"W.WW.WPW.WW.W",

"W.WW.WWWW.WWEW",

"W.............W",

"WWWWWWWWWWWWWWW"

]);

AWmeansthatthereshouldbeawallinthatlocation,Estandsforanenemy,Pfortheplayer,andadotforadotthatPacMancaneat.

Toparsealevel,wewillfirstsplitthesestringsintoanarrayofarrays,ourgrid:

functionparseLevel(data:string[]):Level{

constgrid=data.map(row=>row.split(""));

WewillcreateafunctionmapBoard,whichwilltransformthisgridintoanarrayofobjects.toObjectcreatesanobjectifthegridcontainsthespecifiedcharacterinthatlocation:

return{

walls:mapBoard(toWall),

dots:mapBoard(toObject(".")),

enemies:mapBoard(toEnemy),

player:mapBoard(toObject("P"))[0],

width:grid[0].length,

height:grid.length,

inputMovement:Movement.None,

currentMovement:Movement.None,

difficulty:Difficulty.Easy

};

InmapBoard,wefirstapplythecallbacktoeachelementofthegrid.Wethenflattenthegridtoaonedimensionalarray.Wefilterelementsthatareundefinedoutofthisarray,asthecallbackshouldreturnundefinedwhentheelementinthegridatthatlocationisnottheexpectedkind:

functionmapBoard<U>(callback:(field:string,x:number,y:number)=>U|

undefined):U[]{

Page 982: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

constmapped=grid.map((row,y)=>row.map((field,x)=>

callback(field,x,y)));

returnflatten(mapped).filter(item=>item!==undefined)

asU[];

}

Wehavetocastthevalueinthereturn-statement.TheTypeScriptcompilercannotfollowthatthecalltofilteronlypassesthroughvaluesthatarenotundefined.

IntoObject,wecreateafunctionthatwillcreateanobjectifthegridcontainsthespecifiedcharacteratthatposition.Afunctionthatreturnsafunctioncanbeusedtocurry.Curryingmeansthatyoufirstprovidesomearguments,andlaterontheotherarguments.Inthiscase,weprovidethefirstargument,thekindwithinthereturnstatement,someprecedinglines.TheotherargumentsareprovidedbythemapBoardfunction:

functiontoObject(kind:string){

return(value:string,x:number,y:number)=>{

if(value!==kind)returnundefined;

return{x,y};

}Wewillreturnthecontentofafieldofthegridinget.Iftheindex

isoutofbounds,we

}

Wewillreturnthecontentofafieldofthegridinget.Iftheindexisoutofbounds,wereturnundefined:

functionget(x:number,y:number){

constrow=grid[y];

if(!row)returnundefined;

returnrow[x];

}

Weusethisfunctiontocheckfortheneighborsofawall.Usingthebitwisedefinedenum,wecanregisterallsidesonwhichthewallhasaneighbor:

functiontoWall(kind:string,x:number,y:number):Wall|undefined{

if(kind!=="W")returnundefined;

letneighbours:Side=0;

if(get(x-1,y)==="W")neighbours|=Side.Left;

if(get(x+1,y)==="W")neighbours|=Side.Right;

if(get(x,y-1)==="W")neighbours|=Side.Bottom;

if(get(x,y+1)==="W")neighbours|=Side.Top;

if(get(x-1,y-1)==="W")neighbours|=Side.LeftBottom;

if(get(x-1,y+1)==="W")neighbours|=Side.LeftTop;

if(get(x+1,y-1)==="W")neighbours|=Side.RightBottom;

if(get(x+1,y+1)==="W")neighbours|=Side.RightTop;

return{

x,

y,

neighbours

Page 983: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

}

}

IntoEnemy,wesettheinitiallocationoftheenemyandthetargetlocationtothesamevalues:

functiontoEnemy(kind:string,x:number,y:number){

if(kind!=="E")returnundefined;

return{

x,

y,

toX:x,

toY:y

};

}

}

Finally,wecreateasmallfunctionthatcheckswhetheranobjectisalignedonthegrid:

exportfunctiononGrid({x,y}:Object){

returnisInt(x)&&isInt(y);

}

Wehavenowcreatedthedefaultlevel.Youcaneasilyaddanotherlevellateron,whenthemainmenuhasbeenadded.

Page 984: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthestateWestorethestateinanewinterface.Wewillalsodefinethedefaultstateforourgame:

exportinterfaceState{

level:Level;

}

exportconstdefaultState:State={

level:defaultLevel

};

Thisinterfaceis,atthemoment,notveryusefulasyoumightbebetteroffusingtheLevelasthegamestate.However,lateroninthischapter,wewillalsoaddamenuthatshouldexistinthestate.

Page 985: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

DrawingtheviewInlib/game/view.ts,wewillrenderthegame.Westartwithimportingtypesthatwedefinedearlier:

import{State,Level,Object,Wall,Side,Menu}from"./model";

import{Picture,Pictures,Translate,Scale,Rotate,Rectangle,Line,Circle,

Color,Text,Empty}from"../framework/picture";

Wewillstorethefontnameinavariable,sowecaneasilychangeitlater:

constfont="Arial";

Indraw,wewillrenderthegame.Fornow,thatmeansonlydrawingthelevel.Lateron,wewilladdamenutothegame:

exportfunctiondraw(state:State,width:number,height:number){

drawLevel(state.level,width,height),

}

WerenderthelevelindrawLevel.Wecalculatethesizeofallobjectswiththesizeofthegridandthecanvas:

functiondrawLevel(level:Level,width:number,height:number){

constscale=Math.min(width/(level.width+1),height/(level.height+

1));

Wescaleandcenterthewholelevelwiththiscalculatedscale:

returnnewScale(scale,scale,

newTranslate(-level.width/2+0.5,-level.height/2+0.5,new

Pictures([

Next,wedrawallobjectsonthecanvas.Weuseseveralfunctionsthatwecreateasfollows:

drawObjects(level.walls,drawWall),

drawObjects(level.walls,drawWallLines),

drawObjects(level.dots,drawDot),

drawObjects(level.enemies,drawEnemy),

drawObject(drawPlayer)(level.player)

]))

);

IndrawObject,wedrawanobjectusingthespecifiedcallback.Wetranslatethepictureoftheobjecttotherightlocation:

functiondrawObject<UextendsObject>(callback:(item:U)=>Picture){

return(item:U)=>

newTranslate(item.x,item.y,callback(item));

}

WithdrawObjects,wecandrawalistofobjects:

Page 986: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

functiondrawObjects<UextendsObject>(items:U[],callback:(item:U)=>

Picture){

returnnewPictures(items.map(drawObject(callback)));

}

}

IndrawWall,werenderthebackgroundofawall:

functiondrawWall(){

returnnewColor("#111",newRectangle(0,0,1,1));

}

WerendertheedgesofawallindrawWallLines.Wechecktheneighborsofawallwiththebitwiseenumthatwedefinedearlier.First,welistallpossiblesidesinanarray:

constleftTop:[number,number]=[-0.5,0.5];

constleftBottom:[number,number]=[-0.5,-0.5];

constrightTop:[number,number]=[0.5,0.5];

constrightBottom:[number,number]=[0.5,-0.5];

constwallLines:[Side,Line][]=[

[Side.Left,newLine([leftTop,leftBottom],0.1)],

[Side.Right,newLine([rightTop,rightBottom],0.1)],

[Side.Top,newLine([leftTop,rightTop],0.1)],

[Side.Bottom,newLine([leftBottom,rightBottom],0.1)]

];

Wefilterthisarraywiththebitwiseenum,andcolortheremainingpieces:

functiondrawWallLines({neighbours}:Wall){

constlines=wallLines

.filter(([side])=>(side&neighbours)===0)

.map(([side,line])=>line);

returnnewColor("#0021b3",newPictures(lines));

}

IndrawDot,wewillshowasmallcircleforadot:

functiondrawDot(){

returnnewColor("#f0c0a8",newCircle(0,0,0.2,0.2));

}

Werendertheplayerasacircle.Youcantrytocreatethefamous,eatingPacManyourselflateron:

functiondrawPlayer(){

returnnewColor("#ffff00",newCircle(0,0,0.8,0.8));

}

Wedosomemoreworktodrawanenemy.Theenemywilllookasfollows:

Page 987: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Thebackgroundoftheenemyconsistsofacircleforitshead,arectangleforthebody,andtworotatedrectanglesforthefeet.

functiondrawEnemy(){

constshape=newColor("#ff0000",newPictures([

newCircle(0,0.15,0.6),

newRectangle(0,-0.05,0.6,0.4),

newTranslate(-0.15,-0.25,

newRotate(Math.PI/4,newRectangle(0,0,0.2,Math.SQRT2*0.15))),

newTranslate(0.15,-0.25,

newRotate(Math.PI/4,newRectangle(0,0,0.2,Math.SQRT2*0.15)))

]));

Theeyesconsistoftwowhitecircleswithtwosmallerblackcirclesaspupils.

consteyes=newColor("#fff",newPictures([

newCircle(-0.12,0.15,0.2),

newCircle(0.12,0.15,0.2)

]));

constpupils=newColor("#000",newPictures([

newCircle(-0.12,0.15,0.06),

newCircle(0.12,0.15,0.06)

]));

returnnewPictures([shape,eyes,pupils]);

}

Page 988: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

HandlingeventsWewillcreateaneventhandlerinlib/game/event.ts.Theeventhandlermustsetthecorrectmovementdirectioninthestate.Thetimehandlerwillthenusethistoupdatethedirectionoftheplayer.Thestepcanonlydothatwhentheplayerisalignedtothegrid.Iftheplayerisbetweentwofieldsonthegrid,wewillnotchangethedirectionoftheplayer,sincehewillthenprobablyheadintoawall.

Page 989: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

WorkingwithkeycodesAneventprovidesthekeycodeofthepressedorreleasedkey.Wecangetthiscodeofacertaincharacterwith"x".charCodeAt(0)(wherexisthecharacter).Thekeycodesofleft,top,right,andbottomare37,38,39,and40.

First,wemustcreateahelperfunctionthattransformsakeycodetotheMovementenumthatwedefinedearlier.Westorethedifferentkeysthatweuseinanewenum:

import{KeyEvent,KeyEventKind}from"../framework/event";

import{State,Movement}from"./model";

import{update}from"./utils";

enumKeys{

Top=38,

Left=37,

Bottom=40,

Right=39,

Space="".charCodeAt(0)

}

NowwecantransformakeycodetoaMovement:

functiongetMovement(key:number){

switch(key){

caseKeys.Top:

returnMovement.Top;

caseKeys.Left:

returnMovement.Left;

caseKeys.Bottom:

returnMovement.Bottom;

caseKeys.Right:

returnMovement.Right;

}

returnundefined;

}

TheeventhandlerwillinvokeeventHandlerPlaying,whichwewilldefinelateroninthissection.Whenweaddamenutotheapplication,wewilladjustthishandler:

exportfunctioneventHandler(state:State,event:KeyEvent){

returneventHandlerPlaying(state,event);

}

IneventHandlerPlaying,weupdatethemovementinthestate.Whentheuserpressesakey,wesetthemovementtothatcorrespondingdirection.Whentheuserreleasesthekeythatmapstothecurrentmovement,wesetthemovementtoNone:

functioneventHandlerPlaying(state:State,event:KeyEvent){

if(eventinstanceofKeyEvent){

constinputMovement=getMovement(event.keyCode);

if(event.kind===KeyEventKind.Press){

Page 990: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

if(inputMovement){

returnupdate(state,{

level:update(state.level,{inputMovement})

});

}

}else{

if(inputMovement===state.level.inputMovement){

returnupdate(state,{

level:update(state.level,{inputMovement:Movement.None})

});

}

}

}

returnstate;

}

Wehavenowfinishedtheeventhandlerforthegame.Whentheuserpressesorreleasesakey,thisisupdatedinthestate.However,therealworkisbeingdoneinthetimehandler,whichwecreateinthenextsection.

Page 991: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthetimehandlerThetimehandlerrequiressomemorework.First,weimportothertypesandfunctions.

import{State,Level,Object,Enemy,Wall,Movement,isOppositeMovement,onGrid,

Difficulty}from"./model";

import{update,randomInt,chance,distance,isInt}from"./utils";

Wedefineastepfunctionsothatwecanaddthemenulateron.

exportfunctionstep(state:State){

returnstepLevel(state);

}

InstepLevel,wecanupdatetheobjectsinthelevel.First,weupdatethelocationoftheenemies.WeusestepEnemy,whichwedefinelateron.

functionstepLevel(state:State):State{

constlevel=state.level;

constenemies=level.enemies.map(enemy=>stepEnemy(enemy,level.player,

level.walls,level.difficulty));

Weupdatethelocationoftheplayerbasedonthecurrentmovement:

constplayer=stepPlayer(level.player,level.currentMovement,level.walls);

Dotsthatareneartheplayer,areeatenbytheplayerandremovedfromthelevel:

constdots=stepDots(level.dots,player);

Wechangethecurrentmovementiftheplayerisalignedonthegridorwhentheywantstomoveintheoppositedirection:

constcurrentMovement=onGrid(player)||

isOppositeMovement(level.inputMovement,level.currentMovement)?

level.inputMovement:level.currentMovement;

Weusethesevaluestoupdatethelevel:

constnewLevel=update(level,{enemies,dots,player,currentMovement});

returnupdate(state,{level:newLevel});

}

Now,wecreateafunctionthatcheckswhetheranobjectcollideswithawall:

functioncollidesWall(x:number,y:number,walls:Wall[]){

for(constwallofwalls){

if(Math.abs(wall.x-x)<1&&Math.abs(wall.y-y)<1){

returntrue;

}

}

Page 992: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnfalse;

}

Next,wecreateafunctionthatupdatesthepositionofanenemy.Theenemycanwalk0.0125pointsifthedifficultyiseasy,otherwise,theycanmove0.025point.Thesevaluesarechosensothatafteracertainamountofsteps,theenemyhaswalkedexactly1pointonthegrid.Thus,theenemywillalwaysbealignedtothegridagain:

functionstepEnemy(enemy:Enemy,player:Object,walls:Wall[],difficulty:

Difficulty):Enemy{

constenemyStepSize=difficulty===Difficulty.Easy?0.0125:0.025;

let{x,y,toX,toY}=enemy;

Withacertainchance,theenemywilltargetontheplayeragain.Anenemycannotalwaysseewheretheplayeris,andthechancesimulatesthat.Also,theenemywillgetasmalldeviation:

if(chance(1/(difficulty===Difficulty.Extreme?30:10))){

toX=Math.round(player.x)+randomInt(-2,2);

toY=Math.round(player.y)+randomInt(-2,2);

}

Iftheenemyisalignedonthegrid,itcanmoveinalldirections.Otherwise,itcanonlywalkaheadorback:

if(!isInt(x)){

x+=toX>x?enemyStepSize:-enemyStepSize;

}elseif(!isInt(y)){

y+=toY>y?enemyStepSize:-enemyStepSize;

}else{

Theplayerisalignedonthegrid,butthelocationmighthaveasmallroundingerror.Thus,weroundthevalueshere.

x=Math.round(x);

y=Math.round(y);

Towalkaround,wefirstcreateanarrayofalloptions.Then,wefiltertheseoptionsandsortthem.Withachanceof0.2,theenemywillchoosethesecond-bestoption.Otherwise,itwillchoosethebestoption.Thebestoptionistheoptionthatbringstheenemyasclosetotheenemy:

constoptions:[number,number][]=[

[x+enemyStepSize,y],

[x-enemyStepSize,y],

[x,y+enemyStepSize],

[x,y-enemyStepSize]

];

constpossible=options

.filter(([x,y])=>!collidesWall(x,y,walls))

.sort(compareDistance);

if(possible.length!==0){

if(possible.length>1&&chance(0.2)){

Page 993: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

[x,y]=possible[1];

}

[x,y]=possible[0];

}

}

return{

x,y,toX,toY

};

Attheendofthisfunction,wedefinethecomparefunctionthatweusedtosortthearray.Suchcomparefunctionsshouldreturnanegativevalueifthefirstargumentcomesafterthesecondargument,andapositivevalueifthefirstargumentshouldcomebeforetheother:

functioncompareDistance([x1,y1]:[number,number],[x2,y2]:[number,

number]){

returndistance(toX,toY,x1,y1)-distance(toX,toY,x2,y2);

}

}

WeupdatethelocationoftheplayerinstepPlayer:

constplayerStepSize=0.04;

functionstepPlayer(player:Object,movement:Movement,walls:Wall[]):Object{

let{x,y}=player;

Whentheplayerisalignedonthegrid,weroundthelocationtoeliminateroundingerrors:

if(onGrid(player)){

x=Math.round(x);

y=Math.round(y);

}

Iftheuserhasnomovement,wedonotmodifytheplayerandwecanreturnitdirectly:

switch(movement){

caseMovement.None:

returnplayer;

Otherwise,weupdatethexorycoordinateoftheplayer:

caseMovement.Left:

x-=playerStepSize;

break;

caseMovement.Right:

x+=playerStepSize;

break;

caseMovement.Top:

y+=playerStepSize;

break;

caseMovement.Bottom:

y-=playerStepSize;

break;

}

Page 994: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Iftheuserwasnotalignedonthegrid,wedonothavetocheckwhethertheplayercollideswithawall.Otherwise,wemustvalidateit.Iftheuserthendoescollidewithawall,wereturntheoldplayerwiththeoldlocation:

if(onGrid(player)&&collidesWall(x,y,walls)){

returnplayer;

}

return{x,y};

}

WecanfilterthedotsbycalculatingthedistancetoPacMan.Whentheyareclosetotheplayer,theyareeatenbyPacManandfilteredout:

functionstepDots(dots:Object[],player:Object){

returndots.filter(dot=>distance(dot.x,dot.y,player.x,player.y)>=0.55)

}

Thetimehandlercannowupdatethestate:theplayermoves,theenemiestrytomovetowardtheplayer,andtheplayercaneatdots.

Page 995: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RunningthegameTostartthegame,wemustcallthegamefunctionwiththedefaultstate,drawfunction,timehandler,andeventhandler.Inlib/game/index.ts,wewritethefollowingcodetostartthegame:

import{game}from"../framework/game";

import{defaultState}from"./model";

import{draw}from"./view";

import{step}from"./step";

import{eventHandler}from"./event";

constcanvas=<HTMLCanvasElement>document.getElementById("game");

game(canvas,document.body,60,defaultState,draw,step,eventHandler);

Wecancompilethegamebyexecutinggulp.Youcanplaythegamebyopeningstatic/index.html.

Asyouwillsee,nothinghappenswhenyouhaveeatenallofthedots,orwhenyougethitbyanenemy.Inthenextsection,wewillimplementamenu.Whentheplayerwinsorloses,wewillshowthismenu.

Page 996: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingamenuTofinishoffthegame,wewilladdsomemenustoit.Inthemainmenu,theusercanchooseadifficulty.Theusercanselectanoptionusingthearrowkeysandconfirmusingthespacebar.Themenuwilllooklikethis:

Toimplementthemenu,wemustaddittothestate.Thenwecanrenderthemenuandupdatethemenustateintheeventhandler.Westartbyupdatingthestate.

Page 997: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ChangingthemodelInlib/game/model.ts,wewilladdthemenustothestate.First,wewillcreateanewtypeforthemenu.Themenucontainsatitle,alistofoptions,andtheindexoftheselectedbutton.Eachoptionhasastringandafunctionthatappliestheactionbytransformingthestate:

exportinterfaceMenu{

title:string;

options:[string,(state:State)=>State][];

selected:number;

}

WeaddthemenutotheState:

exportinterfaceState{

menu:Menu|undefined;

level:Level;

}

Themainmenuwillcontainthreebuttons;tostartaneasy,hard,orextremegame.Wewilldefineafunctionthatcanstartthegamewithaspecifieddifficulty:

conststartGame=(difficulty:Difficulty)=>(state:State)=>({

menu:undefined,

level:update(defaultLevel,{difficulty})

});

Nowwecandefinethemainmenu:

exportconstmenuMain:Menu={

title:"PacMan",

options:[

["Easy",startGame(Difficulty.Easy)],

["Hard",startGame(Difficulty.Hard)],

["Extreme",startGame(Difficulty.Extreme)]

],

selected:0

}

Wecandefinetwomoremenus,whichareshownwhentheuserwinsordies:

exportconstmenuWon:Menu={

title:"Youwon!",

options:[

["Back",state=>({menu:menuMain,level:state.level})]

],

selected:0

}

exportconstmenuLost:Menu={

title:"Gameover!",

options:[

["Back",state=>({menu:menuMain,level:state.level})]

],

Page 998: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

selected:0

}

Wecanusethismenuinthestartingstateoftheapplication:

exportconstdefaultState:State={

menu:menuMain,

level:defaultLevel

};

Sincethemenuisapartofthedefaultstate,thegamewillstartwiththemenu.Inthenextsections,wewillrenderthemenuandhandleitsevents.

Page 999: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RenderingthemenuWemustupdatelib/game/view.tstodrawthemenuonthecanvas.Wechangethedrawfunction:

exportfunctiondraw(state:State,width:number,height:number){

returnnewPictures([

drawLevel(state.level,width,height),

drawMenu(state.menu,width,height)

]);

}

Next,wecreatedrawMenu,thatwillrenderthelevel.Itwillshowthetitleandthebuttons.Theselectedbuttongetsadifferentcolor:

functiondrawMenu(menu:Menu|undefined,width:number,height:number):

Picture{

if(menu===undefined)returnnewEmpty();

constselected=menu.selected;

constbackground=newColor("rgba(40,40,40,0.8)",new

Rectangle(0,0,width,height));

consttitle=newTranslate(0,200,newScale(4,4,

newColor("#fff",newText(menu.title,font))

));

constoptions=newPictures(menu.options.map(showOption));

returnnewPictures([background,title,options]);

functionshowOption(item:[string,(state:State)=>State],

index:number){

constisSelected=index===selected;

returnnewTranslate(0,100-index*50,newPictures([

newColor(isSelected?"#ff0000":"#000000",

newRectangle(0,0,200,40)),

newColor(isSelected?"#000000":"#ffffff",

newScale(1.6,1.6,newText(item[0],font)))

]));

}

}

Thisfunctionwillnowdrawthemenuwhenitisactive.Wemuststillhandletheeventsofthemenu.Wewilldothatinthenextsection.

Page 1000: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

HandlingeventsInlib/game/event.ts,wewillhandletheeventsforthemenu.Wemustupdatetheindexoftheselecteditemwhentheuserpressestheupordownkey.Whentheuserpressesspace,weexecutetheactionoftheselectedbutton.First,wemustadjusteventHandlertocalleventHandlerMenuwhenthemenuisvisible.

exportfunctioneventHandler(state:State,event:KeyEvent){

if(state.menu){

returneventHandlerMenu(state,event);

}else{

returneventHandlerPlaying(state,event);

}

}

Next,wecreateeventHandlerMenu.

functioneventHandlerMenu(state:State,event:KeyEvent){

if(eventinstanceofKeyEvent&&event.kind===KeyEventKind.Press){

constmenu=state.menu!;

letselected=menu.selected;

switch(event.keyCode){

caseKeys.Top:

selected--;

if(selected<0){

selected=menu.options.length-1;

}

return{

menu:update(menu,{

selected

}),

level:state.level

};

caseKeys.Bottom:

selected++;

if(selected>=menu.options.length){

selected=0;

}

return{

menu:update(menu,{

selected

}),

level:state.level

};

caseKeys.Space:

constoption=menu.options[menu.selected];

returnoption[1](state);

default:

returnstate;

}

}

returnstate;

}

Page 1001: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Youcannavigatethroughthemenuusingthearrowkeysandthespacebar.However,inthebackground,thegameisstillrunning.Inthenextsection,wewillnotupdatethestateofthelevelwhenthemenuisactive.Also,wewillshowamenuwhentheuserhaswonorlost.

Page 1002: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ModifyingthetimehandlerInlib/game/step.ts,wemustshowthemenuwhentheuserwonorlost.Wemustchangetheimport-statementtoimportmenuLostandmenuWonfrommodel:

import{State,Level,Object,Enemy,Wall,Movement,isOppositeMovement,

menuLost,menuWon,onGrid,Difficulty}from"./model";

InnewMenu,wecheckwhethersuchamenushouldbeshown.

functionnewMenu(player:Object,dots:Object[],enemies:Enemy[]){

for(constenemyofenemies){

if(distance(enemy.x,enemy.y,player.x,player.y)<=1){

returnmenuLost;

}

}

if(dots.length===0)returnmenuWon;

returnundefined;

}

InstepLevel,wemustcallthisfunction.

functionstepLevel(state:State):State{

constlevel=state.level;

constenemies=level.enemies.map(enemy=>stepEnemy(enemy,level.player,

level.walls,level.difficulty));

constplayer=stepPlayer(level.player,level.currentMovement,level.walls);

constdots=stepDots(level.dots,player);

constcurrentMovement=onGrid(player)||

isOppositeMovement(level.inputMovement,level.currentMovement)?

level.inputMovement:level.currentMovement;

constmenu=newMenu(player,dots,enemies);

constnewLevel=update(level,{enemies,dots,player,currentMovement});

returnupdate(state,{level:newLevel,menu});

}

Finally,wemustnotcallstepLevelinstepifthemenuisactive.

exportfunctionstep(state:State){

if(state.menu===undefined){

returnstepLevel(state);

}else{

returnstate;

}

}

Wecannowcompilethegameagainwithgulpandrunitbyopeningstatic/index.html.

Page 1003: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wehaveexploredtheHTMLcanvas.Wehaveseenhowwecandesignaframeworktousefunctionalprogramming.Theframeworkprovidesabstractionarounddrawingonthecanvas,whichisnotpure.

WehavebuiltthegamePacMan.ThestructureofthisapplicationwassimilartoaFluxarchitecture,likewesawinthepreviouschapter.

Theenemiesinthisgamearenotverysmart.Theyeasilygetstuckbehindawall.Inthenextchapter,wewilltakealookatanothergame,butwewillonlyfocusontheartificialintelligence(AI).WewillcreateanapplicationthatcanplayTic-Tac-Toewithoutlosing.WewillseehowaMinimaxstrategyworksandhowwecanimplementitinTypeScript.

Page 1004: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter9.PlayingTic-Tac-ToeagainstanAIWebuiltthegamePacManinthepreviouschapter.Theenemieswerenotverysmart;youcaneasilyfoolthem.Inthischapter,wewillbuildagameinwhichthecomputerwillplaywell.ThegameiscalledTic-Tac-Toe.Thegameisplayedbytwoplayersonagrid,usuallythreebythree.Theplayerstrytoplacetheirsymbolsthreeinarow(horizontal,verticalordiagonal).Thefirstplayercanplacecrosses,thesecondplayerplacescircles.Iftheboardisfull,andnoonehasthreesymbolsinarow,itisadraw.

Thegameisusuallyplayedonathree-by-threegridandthetargetistohavethreesymbolsinarow.Tomaketheapplicationmoreinteresting,wewillmakethedimensionandtherowlengthvariable.

Wewillnotcreateagraphicalinterfaceforthisapplication,sincewehavealreadydonethatinChapter6,AdvancedProgramminginTypeScript.Wewillonlybuildthegamemechanicsandtheartificialintelligence(AI).AnAIisaplayercontrolledbythecomputer.Ifimplementedcorrectly,thecomputershouldneverloseonastandardthreebythreegrid.Whenthecomputerplaysagainstthecomputer,itwillresultinadraft.Wewillalsowritevariousunittestsfortheapplication.

Wewillbuildthegameasacommand-lineapplication.Thatmeansyoucanplaythegameinaterminal.Youcaninteractwiththegameonlywithtextinput:

It'splayerone'sturn!

Chooseoneoutoftheseoptions:

1X|X|

-+-+-

|O|

-+-+-

||

2X||X

-+-+-

|O|

-+-+-

||

3X||

-+-+-

X|O|

-+-+-

||

4X||

-+-+-

|O|X

-+-+-

||

5X||

-+-+-

|O|

-+-+-

X||

Page 1005: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

6X||

-+-+-

|O|

-+-+-

|X|

7X||

-+-+-

|O|

-+-+-

||X

Wewillbuildthisapplicationinthefollowingsteps:

CreatingtheprojectstructureAddingutilityfunctionsCreatingthemodelsImplementingtheAIusingMinimaxCreatingtheinterfaceTestingtheAISummary

Page 1006: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtheprojectstructureWewilllocatethesourcefilesinlibandthetestsinlib/test.WeusegulptocompiletheprojectandAVAtoruntests.WecaninstallthedependenciesofourprojectwithNPM:

npminit-y

npminstallavagulpgulp-typescript--save-dev

Ingulpfile.js,weconfiguregulptocompileourTypeScriptfiles:

vargulp=require("gulp");

varts=require("gulp-typescript");

vartsProject=ts.createProject("./lib/tsconfig.json");

gulp.task("default",function(){

returntsProject.src()

.pipe(ts(tsProject))

.pipe(gulp.dest("dist"));

});

Page 1007: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfigureTypeScriptWecandownloadtypedefinitionsforNodeJSwithNPM:

npminstall@types/node--save-dev

WemustexcludebrowserfilesinTypeScript.Inlib/tsconfig.json,weaddtheconfigurationforTypeScript:

{

"compilerOptions":{

"target":"es6",

"module":"commonjs"

}

}

Forapplicationsthatruninthebrowser,youwillprobablywanttotargetES5,sinceES6isnotsupportedinallbrowsers.However,thisapplicationwillonlybeexecutedinNodeJS,sowedonothavesuchlimitations.YouhavetouseNodeJS6orlaterforES6support.

Page 1008: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingutilityfunctionsSincewewillworkalotwitharrays,wecanusesomeutilityfunctions.First,wecreateafunctionthatflattensatwodimensionalarrayintoaonedimensionalarray:

exportfunctionflatten<U>(array:U[][]){

return(<U[]>[]).concat(...array);

}

Next,wecreateafunctionthatreplacesasingleelementofanarraywithaspecifiedvalue.Wewillusefunctionalprogramminginthischapteragain,sowemustuseimmutabledatastructures.Wecanusemapforthis,sincethisfunctionprovidesboththeelementandtheindextothecallback.Withthisindex,wecandeterminewhetherthatelementshouldbereplaced:

exportfunctionarrayModify<U>(array:U[],index:number,newValue:U){

returnarray.map((oldValue,currentIndex)=>

currentIndex===index?newValue:oldValue);

}

Wealsocreateafunctionthatreturnsarandomintegerunderacertainupperbound:

exportfunctionrandomInt(max:number){

returnMath.floor(Math.random()*max);

}

Wewillusethesefunctionsinthenextsessions.

Page 1009: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthemodelsInlib/model.ts,wewillcreatethemodelforourgame.Themodelshouldcontainthegamestate.

Westartwiththeplayer.Thegameisplayedbytwoplayers.Eachfieldofthegridcontainsthesymbolofaplayerornosymbol.Wewillmodelthegridasatwodimensionalarray,whereeachfieldcancontainaplayer:

exporttypeGrid=Player[][];

AplayeriseitherPlayer1,Player2,ornoplayer:

exportenumPlayer{

Player1=1,

Player2=-1,

None=0

}

Wehavegiventhesemembersvaluessowecaneasilygettheopponentofaplayer:

exportfunctiongetOpponent(player:Player):Player{

return-player;

}

Wecreateatypetorepresentanindexofthegrid.Sincethegridistwodimensional,suchanindexrequirestwovalues:

exporttypeIndex=[number,number];

Wecanusethistypetocreatetwofunctionsthatgetorupdateonefieldofthegrid.Weusefunctionalprogramminginthischapter,sowewillnotmodifythegrid.Instead,wereturnanewgridwithonefieldchanged:

exportfunctionget(grid:Grid,[rowIndex,columnIndex]:Index){

constrow=grid[rowIndex];

if(!row)returnundefined;

returnrow[columnIndex];

}

exportfunctionset(grid:Grid,[row,column]:Index,value:Player){

returnarrayModify(grid,row,

arrayModify(grid[row],column,value)

);

}

Page 1010: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ShowingthegridToshowthegametotheuser,wemustconvertagridtoastring.First,wewillcreateafunctionthatconvertsaplayertoastring,thenafunctionthatusesthepreviousfunctiontoshowarow,finallyafunctionthatusesthesefunctionstoshowthecompletegrid.

Thestringrepresentationofagridshouldhavelinesbetweenthefields.Wecreatetheselineswithstandardcharacters(+,-,and|).Thisgivesthefollowingresult:

X|X|O

-+-+-

|O|

-+-+-

X||

Toconvertaplayertothestring,wemustgettheirsymbol.ForPlayer1,thatisacrossandforPlayer2,acircle.Ifafieldofthegridcontainsnosymbol,wereturnaspacetokeepthegridaligned:

functionshowPlayer(player:Player){

switch(player){

casePlayer.Player1:

return"X";

casePlayer.Player2:

return"O";

default:

return"";

}

}

Wecanusethisfunctiontothetokensofallfieldsinarow.Weaddaseparatorbetweenthesefields:

functionshowRow(row:Player[]){

returnrow.map(showPlayer).reduce((previous,current)=>previous+"|"+

current);

}

Sincewemustdothesamelateron,butwithadifferentseparator,wecreateasmallhelperfunctionthatdoesthisconcatenationbasedonaseparator:

constconcat=(separator:string)=>(left:string,right:string)=>

left+separator+right;

Thisfunctionrequirestheseparatorandreturnsafunctionthatcanbepassedtoreduce.WecannowusethisfunctioninshowRow:

functionshowRow(row:Player[]){

returnrow.map(showPlayer).reduce(concat("|"));

}

Page 1011: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wecanalsousethishelperfunctiontoshowtheentiregrid.Firstwemustcomposetheseparator,whichisalmostthesameasshowingasinglerow.Next,wecanshowthegridwiththisseparator:

exportfunctionshowGrid(grid:Grid){

constseparator="\n"+grid[0].map(()=>"-").reduce(concat("+"))+"\n";

returngrid.map(showRow).reduce(concat(separator));

}

Page 1012: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingoperationsonthegridWewillnowcreatesomefunctionsthatdooperationsonthegrid.Thesefunctionscheckwhethertheboardisfull,whethersomeonehaswon,andwhatoptionsaplayerhas.

Wecancheckwhethertheboardisfullbylookingatallfields.Ifnofieldexiststhathasnosymbolonit,theboardisfull,aseveryfieldhasasymbol:

exportfunctionisFull(grid:Grid){

for(constrowofgrid){

for(constfieldofrow){

if(field===Player.None)returnfalse;

}

}

returntrue;

}

Tocheckwhetherauserhaswon,wemustgetalistofallhorizontal,verticalanddiagonalrows.Foreachrow,wecancheckwhetherarowconsistsofacertainamountofthesamesymbolsonarow.Westorethegridasanarrayofthehorizontalrows,sowecaneasilygetthoserows.Wecanalsogettheverticalrowsrelativelyeasily:

functionallRows(grid:Grid){

return[

...grid,

...grid[0].map((field,index)=>getVertical(index)),

...

];

functiongetVertical(index:number){

returngrid.map(row=>row[index]);

}

}

Gettingadiagonalrowrequiressomemorework.Wecreateahelperfunctionthatwillwalkonthegridfromastartpoint,inacertaindirection.Wedistinguishtwodifferentkindsofdiagonals:adiagonalthatgoestothelower-rightandadiagonalthatgoestothelower-left.

Forastandardthreebythreegame,onlytwodiagonalsexist.However,alargergridmayhavemorediagonals.Ifthegridis5by5,andtheusersshouldgetthreeinarow,tendiagonalswithalengthofatleastthreeexist:

1. 0,0to4,42. 0,1to3,43. 0,2to2,44. 1,0to4,35. 2,0to4,26. 4,0to0,47. 3,0to0,38. 2,0to0,2

Page 1013: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

9. 4,1to1,410. 4,2to2,4

Thediagonalsthatgotowardthelower-right,startatthefirstcolumnoratthefirsthorizontalrow.Otherdiagonalsstartatthelastcolumnoratthefirsthorizontalrow.Inthisfunction,wewilljustreturnalldiagonals,eveniftheyonlyhaveoneelement,sincethatiseasytoimplement.

Weimplementthiswithafunctionthatwalksthegridtofindthediagonal.Thatfunctionrequiresastartpositionandastepfunction.Thestepfunctionincrementsthepositionforaspecificdirection:

functionallRows(grid:Grid){

return[

...grid,

...grid[0].map((field,index)=>getVertical(index)),

...grid.map((row,index)=>getDiagonal([index,0],stepDownRight)),

...grid[0].slice(1).map((field,index)=>getDiagonal([0,index+1],

stepDownRight)),

...grid.map((row,index)=>getDiagonal([index,grid[0].length-1],

stepDownLeft)),

...grid[0].slice(1).map((field,index)=>getDiagonal([0,index],

stepDownLeft))

];

functiongetVertical(index:number){

returngrid.map(row=>row[index]);

}

functiongetDiagonal(start:Index,step:(index:Index)=>

Index){

constrow:Player[]=[];

letindex:Index|undefined=start;

letvalue=get(grid,index);

while(value!==undefined){

row.push(value);

index=step(index);

value=get(grid,index);

}

returnrow;

}

functionstepDownRight([i,j]:Index):Index{

return[i+1,j+1];

}

functionstepDownLeft([i,j]:Index):Index{

return[i+1,j-1];

}

functionstepUpRight([i,j]:Index):Index{

return[i-1,j+1];

}

}

Tocheckwhetherarowhasacertainamountofthesameelementsonarow,wewillcreatea

Page 1014: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

functionwithsomenicelookingfunctionalprogramming.Thefunctionrequiresthearray,theplayer,andtheindexatwhichthecheckingstarts.Thatindexwillusuallybezero,butduringrecursionwecansetittoadifferentvalue.originalLengthcontainstheoriginallengththatasequenceshouldhave.Thelastparameter,length,willhavethesamevalueinmostcases,butinrecursionwewillchangethevalue.Westartwithsomebasecases.Everyrowcontainsasequenceofzerosymbols,sowecanalwaysreturntrueinsuchacase:

functionisWinningRow(row:Player[],player:Player,index:number,

originalLength:number,length:number):boolean{

if(length===0){

returntrue;

}

Iftherowdoesnotcontainenoughelementstoformasequence,therowwillnothavesuchasequenceandwecanreturnfalse:

if(index+length>row.length){

returnfalse;

}

Forothercases,weuserecursion.Ifthecurrentelementcontainsasymboloftheprovidedplayer,thisrowformsasequenceifthenextlength-1fieldscontainthesamesymbol:

if(row[index]===player){

returnisWinningRow(row,player,index+1,originalLength,length-1);

}

Otherwise,therowshouldcontainasequenceoftheoriginallengthinsomeotherposition:

returnisWinningRow(row,player,index+1,originalLength,originalLength);

}

Ifthegridislargeenough,arowcouldcontainalongenoughsequenceafterasequencethatwastooshort.Forinstance,XXOXXXcontainsasequenceoflengththree.ThisfunctionhandlestheserowscorrectlywiththeparametersoriginalLengthandlength.

Finally,wemustcreateafunctionthatreturnsallpossiblesetsthataplayercando.Toimplementthisfunction,wemustfirstfindallindices.Wefiltertheseindicestoindicesthatreferenceanemptyfield.Foreachoftheseindices,wechangethevalueofthegridintothespecifiedplayer.Thisresultsinalistofoptionsfortheplayer:

exportfunctiongetOptions(grid:Grid,player:Player){

constrowIndices=grid.map((row,index)=>index);

constcolumnIndices=grid[0].map((column,index)=>index);

constallFields=flatten(rowIndices.map(

row=>columnIndices.map(column=><Index>[row,column])

));

returnallFields

.filter(index=>get(grid,index)===Player.None)

Page 1015: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

.map(index=>set(grid,index,player));

}

TheAIwillusethistochoosethebestoptionandahumanplayerwillgetamenuwiththeseoptions.

Page 1016: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingthegridBeforethegamecanbestarted,wemustcreateanemptygrid.Wewillwriteafunctionthatcreatesanemptygridwiththespecifiedsize:

exportfunctioncreateGrid(width:number,height:number){

constgrid:Grid=[];

for(leti=0;i<height;i++){

grid[i]=[];

for(letj=0;j<width;j++){

grid[i][j]=Player.None;

}

}

returngrid;

}

Inthenextsection,wewilladdsometestsforthefunctionsthatwehavewritten.Thesefunctionsworkonthegrid,soitwillbeusefultohaveafunctionthatcanparseagridbasedonastring.

Wewillseparatetherowsofagridwithasemicolon.Eachrowcontainstokensforeachfield.Forinstance,"XXO;O;X"resultsinthisgrid:

X|X|O

-+-+-

|O|

-+-+-

X||

Wecanimplementthisbysplittingthestringintoanarrayoflines.Foreachline,wesplitthelineintoanarrayofcharacters.WemapthesecharacterstoaPlayervalue:

exportfunctionparseGrid(input:string){

constlines=input.split(";");

returnlines.map(parseLine);

functionparseLine(line:string){

returnline.split("").map(parsePlayer);

}

functionparsePlayer(character:string){

switch(character){

case"X":

returnPlayer.Player1;

case"O":

returnPlayer.Player2;

default:

returnPlayer.None;

}

}

}

Inthenextsectionwewillusethisfunctiontowritesometests.

Page 1017: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingtestsJustlikeinChapter5,NativeQRScannerApp,wewilluseAVAtowritetestsforourapplication.Sincethefunctionsdonothavesideeffects,wecaneasilytestthem.

Inlib/test/winner.ts,wetestthefindWinnerfunction.First,wecheckwhetherthefunctionrecognizesthewinnerinsomesimplecases:

importtestfrom"ava";

import{Player,parseGrid,findWinner}from"../model";

test("playerwinner",t=>{

t.is(findWinner(parseGrid(";XXX;"),3),Player.Player1);

t.is(findWinner(parseGrid(";OOO;"),3),Player.Player2);

t.is(findWinner(parseGrid(";;"),3),Player.None);

});

Wecanalsotestallpossiblethree-in-a-rowpositionsinthethreebythreegrid.Withthistest,wecanfindoutwhetherhorizontal,vertical,anddiagonalrowsarecheckedcorrectly:

test("3x3winner",t=>{

constgrids=[

"XXX;;",

";XXX;",

";;XXX",

"X;X;X",

"X;X;X",

"X;X;X",

"X;X;X",

"X;X;X"

];

for(constgridofgrids){

t.is(findWinner(parseGrid(grid),3),Player.Player1);

}

});

Wemustalsotestthatthefunctiondoesnotclaimthatsomeonewontoooften.Inthenexttest,wevalidatethatthefunctiondoesnotreturnawinnerforgridsthatdonothaveawinner:

test("3x3nowinner",t=>{

constgrids=[

"XXO;OXX;XOO",

";;",

"XXO;;OOX",

"X;X;X"

];

for(constgridofgrids){

t.is(findWinner(parseGrid(grid),3),Player.None);

}

});

Sincethegamealsosupportsotherdimensions,weshouldcheckthesetoo.Wecheckthatalldiagonalsofafourbythreegridarecheckedcorrectly,wherethelengthofasequenceshould

Page 1018: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

betwo:

test("4x3winner",t=>{

constgrids=[

"X;X;",

"X;X;",

"X;X;",

";X;X",

"X;X;",

"X;X;",

"X;X;",

";X;X"

];

for(constgridofgrids){

t.is(findWinner(parseGrid(grid),2),Player.Player1);

}

});

Youcanofcourseaddmoretestgridsyourself.

Tip

Addtestsbeforeyoufixabug.Thesetestsshouldshowthewrongbehaviorrelatedtothebug.Whenyouhavefixedthebug,thesetestsshouldpass.Thispreventsthebugreturninginafutureversion.

Page 1019: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RandomtestingInsteadofrunningthetestonsomepredefinedsetoftestcases,youcanalsowriteteststhatrunonrandomdata.Youcannotcomparetheoutputofafunctiondirectlywithanexpectedvalue,butyoucanchecksomepropertiesofit.Forinstance,getOptionsshouldreturnanemptylistifandonlyiftheboardisfull.WecanusethispropertytotestgetOptionsandisFull.

First,wecreateafunctionthatrandomlychoosesaplayer.Tohigherthechanceofafullgrid,weaddsomeextraweightontheplayerscomparedtoanemptyfield:

importtestfrom"ava";

import{createGrid,Player,isFull,getOptions}from"../model";

import{randomInt}from"../utils";

functionrandomPlayer(){

switch(randomInt(4)){

case0:

case1:

returnPlayer.Player1;

case2:

case3:

returnPlayer.Player2;

default:

returnPlayer.None;

}

}

Wecreate10000randomgridswiththisfunction.Thedimensionsandthefieldsarechosenrandomly:

test("get-options",t=>{

for(leti=0;i<10000;i++){

constgrid=createGrid(randomInt(10)+1,randomInt(10)+1)

.map(row=>row.map(randomPlayer));

Next,wecheckwhetherthepropertythatwedescribeholdsforthisgrid:

constoptions=getOptions(grid,Player.Player1)

t.is(isFull(grid),options.length===0);

Wealsocheckthatthefunctiondoesnotgivethesameoptiontwice:

for(leti=1;i<options.length;i++){

for(letj=0;j<i;j++){

t.notSame(options[i],options[j]);

}

}

}

});

Dependingonhowcriticalafunctionis,youcanaddmoretests.Inthiscase,youcouldcheck

Page 1020: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

thatonlyonefieldismodifiedinanoptionorthatonlyanemptyfieldcanbemodifiedinanoption:

Nowyoucanrunthetestsusinggulp&&avadist/test.Youcanaddthistoyourpackage.jsonfile.Inthescriptssection,youcanaddcommandsthatyouwanttorun.Withnpmrunxxx,youcanruntaskxxx.npmtestthatwasaddedasshorthandfornpmruntest,sincethetestcommandisoftenused:

{

"name":"chapter-7",

"version":"1.0.0",

"scripts":{

"test":"gulp&&avadist/test"

},

...

Page 1021: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingtheAIusingMinimaxWecreateanAIbasedonMinimax.Thecomputercannotknowwhathisopponentwilldointhenextsteps,buthecancheckwhathecandointheworst-case.Theminimumoutcomeoftheseworstcasesismaximizedbythisalgorithm.ThisbehaviorhasgivenMinimaxitsname.

TolearnhowMinimaxworks,wewilltakealookatthevalueorscoreofagrid.Ifthegameisfinished,wecaneasilydefineitsvalue:ifyouwon,thevalueis1;ifyoulost,-1andifitisadraw,0.Thus,forplayer1thenextgridhasvalue1andforplayer2thevalueis-1:

X|X|X

-+-+-

O|O|

-+-+-

X|O|

Wewillalsodefinethevalueofagridforagamethathasnotbeenfinished.Wetakealookatthefollowinggrid:

X||X

-+-+-

O|O|

-+-+-

O|X|

Itisplayer1'sturn.Hecanplacehisstoneonthetoprow,andhewouldwin,resultinginavalueof1.Hecanalsochoosetolayhisstoneonthesecondrow.Thenthegamewillresultinadraft,ifplayer2isnotdumb,withscore0.Ifhechoosestoplacethestoneonthelastrow,player2canwinresultingin-1.Weassumethatplayer1issmartandthathewillgoforthefirstoption.Thus,wecouldsaythatthevalueofthisunfinishedgameis1.

Wewillnowformalizethis.Inthepreviousparagraph,wehavesummedupalloptionsfortheplayer.Foreachoption,wehavecalculatedtheminimumvaluethattheplayercouldgetifhewouldchoosethatoption.Fromtheseoptions,wehavechosenthemaximumvalue.

Minimaxchoosestheoptionwiththehighestvalueofalloptions.

Page 1022: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ImplementingMinimaxinTypeScriptAsyoucansee,thedefinitionofMinimaxlookslikeyoucanimplementitwithrecursion.Wecreateafunctionthatreturnsboththebestoptionandthevalueofthegame.Afunctioncanonlyreturnasinglevalue,butmultiplevaluescanbecombinedintoasinglevalueinatuple,whichisanarraywiththesevalues.

First,wehandlethebasecases.Ifthegameisfinished,theplayerhasnooptionsandthevaluecanbecalculateddirectly:

import{Player,Grid,findWinner,isFull,getOpponent,getOptions}from

"./model";

exportfunctionminimax(grid:Grid,rowLength:number,player:Player):[Grid,

number]{

constwinner=findWinner(grid,rowLength);

if(winner===player){

return[undefined,1];

}elseif(winner!==Player.None){

return[undefined,-1];

}elseif(isFull(grid)){

return[undefined,0];

Otherwise,welistalloptions.Foralloptions,wecalculatethevalue.Thevalueofanoptionisthesameastheoppositeofthevalueoftheoptionfortheopponent.Finally,wechoosetheoptionwiththebestvalue:

}else{

letoptions=getOptions(grid,player);

constopponent=getOpponent(player);

returnoptions.map<[Grid,number]>(

option=>[option,-(minimax(option,rowLength,opponent)[1])]

).reduce(

(previous,current)=>previous[1]<current[1]?current:previous

)!;

}

}

Whenyouusetupletypes,youshouldexplicitlyaddatypedefinitionforit.Sincetuplesarearraystoo,anarraytypeisautomaticallyinferred.Whenyouaddthetupleasreturntype,expressionsafterthereturnkeywordwillbeinferredasthesetuples.Foroptions.map,youcanmentiontheuniontypeasatypeargumentorbyspecifyingitinthecallbackfunction(options.map((option):[Grid,number]=>...);).

YoucaneasilyseethatsuchanAIcanalsobeusedforotherkindsofgames.Actually,theminimaxfunctionhasnodirectreferencetoTic-Tac-Toe,onlyfindWinner,isFullandgetOptionsarerelatedtoTic-Tac-Toe.

Page 1023: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

OptimizingthealgorithmTheMinimaxalgorithmcanbeslow.Choosingthefirstset,especially,takesalongtimesincethealgorithmtriesallwaysofplayingthegame.Wewillusetwotechniquestospeedupthealgorithm.

First,wecanusethesymmetryofthegame.Whentheboardisemptyitdoesnotmatterwhetheryouplaceastoneintheupper-leftcornerorthelower-rightcorner.Rotatingthegridaroundthecenter180degreesgivesanequivalentboard.Thus,weonlyneedtotakealookathalftheoptionswhentheboardisempty.

Secondly,wecanstopsearchingforoptionsifwefoundanoptionwithvalue1.Suchanoptionisalreadythebestthingtodo.

Implementingthesetechniquesgivesthefollowingfunction:

import{Player,Grid,findWinner,isFull,getOpponent,getOptions}from

"./model";

exportfunctionminimax(grid:Grid,rowLength:number,player:Player):[Grid,

number]{

constwinner=findWinner(grid,rowLength);

if(winner===player){

return[undefined,1];

}elseif(winner!==Player.None){

return[undefined,-1];

}elseif(isFull(grid)){

return[undefined,0];

}else{

letoptions=getOptions(grid,player);

constgridSize=grid.length*grid[0].length;

if(options.length===gridSize){

options=options.slice(0,Math.ceil(gridSize/2));

}

constopponent=getOpponent(player);

letbest:[Grid,number]|undefined=undefined;

for(constoptionofoptions){

constcurrent:[Grid,number]=[option,-(minimax(option,rowLength,

opponent)[1])];

if(current[1]===1){

returncurrent;

}elseif(best===undefined||current[1]>best[1]){

best=current;

}

}

returnbest!;

}

}

ThiswillspeeduptheAI.InthenextsectionswewillimplementtheinterfaceforthegameandwewillwritesometestsfortheAI.

Page 1024: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingtheinterfaceNodeJScanbeusedtocreateservers,aswedidinchapters2and3.Youcanalsocreatetoolswithacommandlineinterface(CLI).Forinstance,gulp,NPMandtypingsarecommandlineinterfacesbuiltwithNodeJS.WewilluseNodeJStocreatetheinterfaceforourgame.

Page 1025: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

HandlinginteractionTheinteractionfromtheusercanonlyhappenbytextinputintheterminal.Whenthegamestarts,itwillasktheusersomequestionsabouttheconfiguration:width,height,rowlengthforasequence,andtheplayer(s)thatareplayedbythecomputer.Thehighlightedlinesaretheinputoftheuser:

Tic-Tac-Toe

Width

3

Height

3

Rowlength

2

Whocontrolsplayer1?

1You

2Computer

1

Whocontrolsplayer2?

1You

2Computer

1

Duringthegame,thegameaskstheuserwhichofthepossibleoptionshewantstodo.Allpossiblestepsareshownonthescreen,withanindex.Theusercantypetheindexoftheoptionhewants:

X||

-+-+-

O|O|

-+-+-

|X|

It'splayerone'sturn!

Chooseoneoutoftheseoptions:

1X|X|

-+-+-

O|O|

-+-+-

|X|

2X||X

-+-+-

O|O|

-+-+-

|X|

3X||

-+-+-

O|O|X

-+-+-

|X|

Page 1026: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

4X||

-+-+-

O|O|

-+-+-

X|X|

5X||

-+-+-

O|O|

-+-+-

|X|X

ANodeJSapplicationhasthreestandardstreamstointeractwiththeuser.Standardinput,stdin,isusedtoreceiveinputfromtheuser.Standardoutput,stdout,isusedtoshowtexttotheuser.Standarderror,stderr,isusedtoshowerrormessagestotheuser.Youcanaccessthesestreamswithprocess.stdin,process.stdoutandprocess.stderr.

Youhaveprobablyalreadyusedconsole.logtowritetexttotheconsole.Thisfunctionwritesthetexttostdout.Wewilluseconsole.logtowritetexttostdoutandwewillnotusestderr.

Wewillcreateahelperfunctionthatreadsalinefromstdin.Thisisanasynchronoustask,thefunctionstartslisteningandresolveswhentheuserhitsenter.Inlib/cli.ts,westartbyimportingthetypesandfunctionthatwehavewritten.

import{Grid,Player,getOptions,getOpponent,showGrid,findWinner,isFull,

createGrid}from"./model";

import{minimax}from"./ai";

Wecanlistentoinputfromstdinusingthedataevent.Theprocesssendseitherthestringorabuffer,anefficientwaytostorebinarydatainmemory.Withonce,thecallbackwillonlybefiredonce.Ifyouwanttolistentoalloccurrencesoftheevent,youcanuseon:

functionreadLine(){

returnnewPromise<string>(resolve=>{

process.stdin.once("data",(data:string|Buffer)=>

resolve(data.toString()));

});

}

WecaneasilyusereadLineinasyncfunctions.Forinstance,wecannowcreateafunctionthatreads,parsesandvalidatesaline.Wecanusethistoreadtheinputoftheuser,parseittoanumber,andfinallycheckthatthenumberiswithinacertainrange.Thisfunctionwillreturnthevalueifitpassesthevalidator.Otherwiseitshowsamessageandretries.

asyncfunctionreadAndValidate<U>(message:string,parse:(data:string)=>U,

validate:(value:U)=>boolean):Promise<U>{

constdata=awaitreadLine();

constvalue=parse(data);

if(validate(value)){

returnvalue;

}else{

console.log(message);

Page 1027: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

returnreadAndValidate(message,parse,validate);

}

}

Wecanusethisfunctiontoshowaquestionwheretheuserhasvariousoptions.Theusershouldtypetheindexofhisanswer.Thisfunctionvalidatesthattheindexiswithinbounds.Wewillshowindicesstartingat1totheuser,sowemustcarefullyhandlethese.

asyncfunctionchoose(question:string,options:string[]){

console.log(question);

for(leti=0;i<options.length;i++){

console.log((i+1)+"\t"+options[i].replace(/\n/g,"\n\t"));

console.log();

}

returnawaitreadAndValidate(

`Enteranumberbetween1and${options.length}`,

parseInt,

index=>index>=1&&index<=options.length

)-1;

}

Page 1028: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CreatingplayersAplayercouldeitherbeahumanorthecomputer.Wecreateatypethatcancontainbothkindsofplayers.

typePlayerController=(grid:Grid)=>Grid|Promise<Grid>;

Nextwecreateafunctionthatcreatessuchaplayer.Forauser,wemustfirstknowwhetherheisthefirstorthesecondplayer.Thenwereturnanasyncfunctionthataskstheplayerwhichstephewantstodo.

constgetUserPlayer=(player:Player)=>async(grid:Grid)=>{

constoptions=getOptions(grid,player);

constindex=awaitchoose("Chooseoneoutoftheseoptions:",

options.map(showGrid));

returnoptions[index];

};

FortheAIplayer,wemustknowtheplayerindexandthelengthofasequence.WeusethesevariablesandthegridofthegametoruntheMinimaxalgorithm.

constgetAIPlayer=(player:Player,rowLength:number)=>(grid:Grid)=>

minimax(grid,rowLength,player)[0]!;

Nowwecancreateafunctionthataskstheplayerwhetheraplayershouldbeplayedbytheuserorthecomputer.

asyncfunctiongetPlayer(index:number,player:Player,rowLength:number):

Promise<PlayerController>{

switch(awaitchoose(`Whocontrolsplayer${index}?`,["You","Computer"]))

{

case0:

returngetUserPlayer(player);

default:

returngetAIPlayer(player,rowLength);

}

}

Wecombinethesefunctionsinafunctionthathandlesthewholegame.First,wemustasktheusertoprovidethewidth,heightandlengthofasequence.

exportasyncfunctiongame(){

console.log("Tic-Tac-Toe");

console.log();

console.log("Width");

constwidth=awaitreadAndValidate("Enteraninteger",parseInt,isFinite);

console.log("Height");

constheight=awaitreadAndValidate("Enteraninteger",parseInt,isFinite);

console.log("Rowlength");

constrowLength=awaitreadAndValidate("Enteraninteger",parseInt,

isFinite);

Page 1029: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Weasktheuserwhichplayersshouldbecontrolledbythecomputer.

constplayer1=awaitgetPlayer(1,Player.Player1,rowLength);

constplayer2=awaitgetPlayer(2,Player.Player2,rowLength);

Theusercannowplaythegame.Wedonotusealoop,butweuserecursiontogivetheplayertheirturns.

returnplay(createGrid(width,height),Player.Player1);

asyncfunctionplay(grid:Grid,player:Player):Promise<[Grid,Player]>{

Ineverystep,weshowthegrid.Ifthegameisfinished,weshowwhichplayerhaswon.

console.log();

console.log(showGrid(grid));

console.log();

constwinner=findWinner(grid,rowLength);

if(winner===Player.Player1){

console.log("Player1haswon!");

return<[Grid,Player]>[grid,winner];

}elseif(winner===Player.Player2){

console.log("Player2haswon!");

return<[Grid,Player]>[grid,winner];

}elseif(isFull(grid)){

console.log("It'sadraw!");

return<[Grid,Player]>[grid,Player.None];

}

Ifthegameisnotfinished,weaskthecurrentplayerorthecomputerwhichsethewantstodo.

console.log(`It'splayer${player===Player.Player1?"one's":"two's"}

turn!`);

constcurrent=player===Player.Player1?player1:player2;

returnplay(awaitcurrent(grid),getOpponent(player));

}

}

Inlib/index.ts,wecanstartthegame.Whenthegameisfinished,wemustmanuallyexittheprocess.

import{game}from"./cli";

game().then(()=>process.exit());

Wecancompileandrunthisinaterminal:

gulp&&node--harmony_destructuringdist

Atthetimeofwriting,NodeJSrequiresthe--harmony_destructuringflagtoallowdestructuring,like[x,y]=z.InfutureversionsofNodeJS,thisflagwillberemovedand

Page 1030: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

youcanrunitwithoutit.

Page 1031: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheAIWewilladdsometeststocheckthattheAIworksproperly.Forastandardthreebythreegame,theAIshouldneverlose.ThatmeanswhenanAIplaysagainstanAI,itshouldresultinadraw.Wecanaddatestforthis.Inlib/test/ai.ts,weimportAVAandourowndefinitions.

importtestfrom"ava";

import{createGrid,Grid,findWinner,isFull,getOptions,Player}from

"../model";

import{minimax}from"../ai";

import{randomInt}from"../utils";

Wecreateafunctionthatsimulatesthewholegameplay.

typePlayerController=(grid:Grid)=>Grid;

functionrun(grid:Grid,a:PlayerController,b:PlayerController):Player{

constwinner=findWinner(grid,3);

if(winner!==Player.None)returnwinner;

if(isFull(grid))returnPlayer.None;

returnrun(a(grid),b,a);

}

WewriteafunctionthatexecutesastepfortheAI.

constaiPlayer=(player:Player)=>(grid:Grid)=>

minimax(grid,3,player)[0]!;

NowwecreatethetestthatvalidatesthatagamewheretheAIplaysagainsttheAIresultsinadraw.

test("AIvsAI",t=>{

constresult=run(createGrid(3,3),aiPlayer(Player.Player1),

aiPlayer(Player.Player2))

t.is(result,Player.None);

});

Page 1032: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingwitharandomplayerWecanalsotestwhathappenswhentheAIplaysagainstarandomplayerorwhenaplayerplaysagainsttheAI.TheAIshouldwinoritshouldresultinadraw.Werunthesemultipletimes;whatyoushouldalwaysdowhenyouuserandomizationinyourtest.

Wecreateafunctionthatcreatestherandomplayer.

constrandomPlayer=(player:Player)=>(grid:Grid)=>{

constoptions=getOptions(grid,player);

returnoptions[randomInt(options.length)];

};

Wewritethetwoteststhatbothrun20gameswitharandomplayerandanAI.

test("randomvsAI",t=>{

for(leti=0;i<20;i++){

constresult=run(createGrid(3,3),randomPlayer(Player.Player1),

aiPlayer(Player.Player2))

t.not(result,Player.Player1);

}

});

test("AIvsrandom",t=>{

for(leti=0;i<20;i++){

constresult=run(createGrid(3,3),aiPlayer(Player.Player1),

randomPlayer(Player.Player2))

t.not(result,Player.Player2);

}

});

Wehavewrittendifferentkindsoftests:

TeststhatchecktheexactresultsofsinglefunctionTeststhatcheckacertainpropertyofresultsofafunctionTeststhatcheckabigcomponent

Alwaysstartwritingtestsforsmallcomponents.IftheAItestsshouldfail,thatcouldbecausedbyamistakeinhasWinner,isFullorgetOptions,soitishardtofindthelocationoftheerror.Onlytestingsmallcomponentsisnotenough;biggertests,suchastheAItests,areclosertowhattheuserwilldo.Biggertestsarehardertocreate,especiallywhenyouwanttotesttheuserinterface.Youmustalsonotforgetthattestscannotguaranteethatyourcoderunscorrectly,itjustguaranteesthatyourtestcasesworkcorrectly.

Page 1033: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wehavewrittenanAIforTic-Tac-Toe.Withthecommandlineinterface,youcanplaythisgameagainsttheAIoranotherhuman.YoucanalsoseehowtheAIplaysagainsttheAI.Wehavewrittenvarioustestsfortheapplication.

YouhavelearnedhowMinimaxworksforturn-basedgames.Youcanapplythistootherturn-basedgamesaswell.Ifyouwanttoknowmoreonstrategiesforsuchgames,youcantakealookatgametheory,themathematicalstudyofthesegames.

ReadingthismeansthatyouhavefinishedtheFunctionalProgrammingpartofthisbook.Onechapterremains,whichwillexplainhowyoucanmigratealegacyJavaScriptcodebasetoTypeScript.

Page 1034: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Chapter10.MigrateJavaScripttoTypeScriptInthepreviouschapters,wehavebuiltnewapplicationsinTypeScript.However,youmightalsohaveoldcodebasesinJavaScriptwhichyouwanttomigratetoTypeScript.WewillseehowtheseprojectscanbeconvertedtoTypeScript.

Youcouldrewritethewholeprojectfromscratch,butthatwouldrequirealotofworkforbigprojects.SinceTypeScriptisbasedonJavaScript,thistransitioncanbedonemoreefficiently.

Sincethemigrationprocessisdependentontheproject,thischaptercanonlygiveguidance.Forvariouscommontopics,thischaptercontainsarecipetomigratecode.Migratingaprojectrequiresknowledgeoftheframeworksandthestructureoftheproject.

Thefollowingstepsarerelatedtomigratingacodebase:

GraduallymigratingtoTypeScriptAddingTypeScriptMigratingeachfileRefactoringtheproject

Page 1035: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

GraduallymigratingtoTypeScriptAsofTypeScript1.8,itispossibletocombineJavaScriptandTypeScriptinthesameproject.Thus,youcanmigrateaprojectfilebyfile.

Duringthemigrationofthefiles,theprojectshouldbeworkingateverystep.Thatway,youcancheckthattheworkisgoingwell,andyoucanstillworkontheproject.Ifanurgentbugisreported,youdonothavetofixitintheoldandmigratedversion;youonlyhavetofixitinthecurrentversion.

Youcanconverttheprojectinthefollowingsteps:

AddtheTypeScriptcompilertotheprojectMigrateeachfileRefactorandenablestricterchecksofTypeScript

Inthenextsections,wewillseehowthesestepscanbedone.

Page 1036: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingTypeScriptBeforeyoucanconvertJavaScriptfilestoTypeScript,youmustaddtheTypeScriptcompilertoaproject.Iftheprojectalreadyusesabuildstep,theTypeScriptcompilermustbeintegratedintothebuildstep.Otherwise,anewbuildstepmustbecreated.Inthefollowingsections,wewillsetupTypeScriptandthebuildsystem.

Page 1037: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringTypeScriptInallcases,youshouldstartwithconfiguringTypeScript.Thisconfigurationwillbeusedbycodeeditorsandthebuildtool.ThemostimportantsettingisallowJs.ThissettingallowsJavaScriptfilesintheTypeScriptproject.Otherimportantoptionsaretargetandmodule.Fortarget,youcanchoosebetweenes3,es5,andes2015.ThelatestversionofJavaScript,es2015,isnotsupportedinallbrowsersatthetimeofwriting.Youcantargetes2015whenyouwriteanapplicationforNodeJS.Youcantargetes5forbrowsers.Forveryoldenvironments,youmusttargetes3.

Iftheprojectusesexternalmodules,youshouldalsosetthemodulesetting.IfyourprojectusesCommonJS,mostlyusedincombinationwithNodeJS,browserifyorwebpack,youshoulduse"module":"commonjs".AnimportinCommonJScanberecognizedbyacalltorequireandanexportbyanassignmenttoexports.[...]ormodule.exports,andfilesarenotwrappedinadefinefunction:

varpath=require("path");

exports.lorem=...;

module.exports=...;

AnothermoduleformatisAMD,AsynchronousModuleDefinition.Thisformatisusedalotforwebapplications.YoucanconfigureTypeScriptforAMDwith"module":"amd".ThemostpopularimplementationofAMDisrequire.js.

AnAMDfilestartswithadefinecall.

define(["require","exports","path"],function(require,exports,path){

exports.lorem=...;

});

Recentprojectsmightusees2015modules,withabuildstep.Youcanrecognizesuchfilesbytheimportandexportkeywords.

import*aspathfrom"path";

exportvarlorem=...;

Ifyouusees2015modules,youcanset"module":"es2015".However,sincethesemodulesareoftenusedwithacertainbuildsteptocompilethesemodulestoCommonJS,AMDorSystemJS,youcanalsodothatdirectlywithTypeScript.TheTypeScriptcompilerwillalsodothistransformationfortheJavaScriptfilesoftheproject,soyoucanremovetheotherbuildstepthatdoesthis(forinstance,Babel).Ifyouwanttodothis,youmustuse"commonjs","amd",or"systemjs".

Ifyourprojectdidnotuseabuildstep,youmightwanttochangethedirectorystructureofyourproject.Youmustnotstorethesourcefiles(TypeScript/JavaScript)inthesamedirectoryasthecompiledfiles(JavaScript).Inthepreviouschapters,weusedlibforthe

Page 1038: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

sourcesanddistforthecompiledfiles.Wecanconfigurethatbysetting"outDir":"dest".Ifyouuseabuildtoolsuchasgulpwheretemporaryfilescanstayinmemoryandarenotwrittentothedisk,youdonotneedtosetthisoptionsincethecompiledfilesarenotdirectlywrittentothedisk.

Thisshouldresultinatsconfig.jsonfilesimilartothisone:

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"outDir":"dist"

}

}

Youshouldplacethisfileinthedirectorythatcontainsthesourcefiles.Ifyourprojectdidnotuseabuildtool,youcannowcompiletheprojectwithtsc-p./lib(where./libreferencesthedirectorythatcontainsthesourcefilesandtsconfig.jsonfile),providedthatyouhaveTypeScriptinstalled(npminstalltypescript-g).Ifyourprojectalreadyusedabuildsystem,youhavetointegratetheTypeScriptcompilerintoit,whichwewilldointhenextsection.

Page 1039: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConfiguringthebuildtoolConfiguringthebuilddependsonthebuildtoolyouuse.Forafewcommonlyusedtools,youcanfindthestepshere.MostbuildtoolsrequireyoutoinstallaTypeScriptplugin.Forbrowserify,youmustinstalltsifyusingNPM;forGrunt,grunt-ts;forgulp,gulp-typescript;andforwebpack,ts-loader.IfyourprojectusesJSPM,youdonothavetoinstallaplugin.

Youcanfindvariousconfigurationswiththesetoolsat:http://www.typescriptlang.org/docs/handbook/integrating-with-build-tools.html.Ifyouuseadifferentbuildtool,youshouldlookinthedocumentationofthetoolandsearchforaTypeScriptplugin.

SinceTypeScriptaccepts(andneeds)theJavaScriptfilesinyourproject,youmustprovideallsourcefilestotheTypeScriptcompiler.Forgulp,thatwouldmeanthatyouaddTypeScriptbeforeothercompilationsteps.Imagineataskinyourgulpfilelookslikethis:

gulp.src("lib/**/*.js")

.pipe(plugin())

.pipe(gulp.dist("dest"));

YoucanaddTypeScripttothisgulpfile:

varts=require("gulp-typescript");

vartsProject=ts.createProject("lib/tsconfig.json");

...

gulp.src("lib/**/*.js")

.pipe(ts(tsProject))

.pipe(plugin())

.pipe(gulp.dist("dest"));

Forabuildtoolthatcannotstoretemporaryfilesinmemory,suchasGrunt,youshouldcompileTypeScripttoatemporarydirectory.Otherstepsfromthebuildshouldreadthesourcesfromthisdirectory.

Formoreinformationonhowtoconfigureaspecificbuildtool,youcanlookatthedocumentationofthetoolandtheplugin.

Page 1040: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AcquiringtypedefinitionsFordependenciesthatyouuse,suchasjQuery,youmustacquiretypedefinitions.Youcaninstallthemusingnpm.Youcanfindthesetypedefinitionsonhttps://aka.ms/types.

Page 1041: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TestingtheprojectBeforegoingtothenextstep,makesurethatthebuildisworking.TypeScriptshouldonlyshowsyntaxerrorsinJavaScriptfiles.Also,theapplicationshouldbeworkingatruntime.

Tip

IfyouarenowusingTypeScripttotranspileESmodulestoCommonJS,youmightrunintoproblems.BabelandTypeScripthandledefaultimportsdifferently.Babellooksforthedefaultproperty,andifthatdoesnotexist,itbehavesthesameasafullmoduleimport.TypeScriptwillonlylookforthedefaultproperty.Ifyougetruntimeerrorsafterthemigration,youmightneedtoreplaceadefaultimport(importnamefrom"package")withafullmoduleimport(import*asnamefrom"package").

Page 1042: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MigratingeachfileWhenthebuildsystemissetup,youcanstartwithmigratingfiles.Itiseasiesttostartwithfilesthatdonotdependonotherfiles,asthesedonotdependontypesofotherfiles.Tomigrateafile,youmustrenamethefileextensionto.ts,convertthemoduleformattoESmodules,correcttypesthatareinferredincorrectly,andaddtypestountypedentities.Inthenextsections,wewilltakealookatthesetasks.

Page 1043: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

ConvertingtoESmodulesInTypeScriptfilesyoucannotuseCommonJSorAMDdirectly.InsteadyoumustuseESmodules,likewedidinthepreviouschapters.Foranimport,youmustchoosefromthese:

import*asnamefrom"package",importsthewholepackage,similartovarname=require("package")inCommonJS.importnamefrom"package",importsthedefaultexport,similartovarname=require("package").default.import{name}from"package",importsanamedexport,similartovarname=require("package").name.

ESmodulesoffervariousconstructstoexportvaluesfrommodules:

exportfunctionname(){},exportclassName{},exportvarname,exportsanamedvariable.Compilesto:

functionname(){}

exports.name=name;

exportdefaultfunctionname(){},exportdefaultclassName{},exportdefaultvarname,exportsavariableasthedefaultexport.Compilesto:

functionname(){}

exports.default=name;

exportdefaultx,wherexisanexpression,exportsanexpressionasthedefaultexport.export{x,y},exportsvariablesxandyasnamedexports.Thiscompilesto:

exports.x=x;

exports.y=y;

WithCommonJSandAMDyoucanalsoexportanexpressionasthefullmodule,withmodule.exports=xinCommonJSorreturnxinAMD.ThisisnotpossiblewithESmodules.Ifthispatternisusedinafilethatyoumustmigrate,youcaneitherswitchtoanESexportorpatchallfilesthatimportthisfile,orusealegacyexportstatementinTypeScript.Withexport=x,youcanexportanexpressionasthefullmodule.However,sincethisisnotstandard,itisnotadvisedtodothisbutitcanhelpduringthemigration.Thiscompilestomodule.exports=xorreturnx.

Thefileshouldgivenosyntaxerrorswhenyoucompileit.Itmightshowtypeerrors,whichthenextsessionwilldiscuss.

Page 1044: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

CorrectingtypesSincethefilewasaJavaScriptfile,itdoesnothaveanytypeannotations.Atsomelocations,TypeScriptwillinfertypesforvariables.Whenyoudeclareavariableanddirectlyassigntoit,TypeScriptwillinferthetypebasedonthetypeoftheassignment.Thus,whenyouwriteletx="",TypeScriptwilltypexasastring.However,insomecasestheinferredtypeisnotcorrect.Youcanseethatinthenextexamples.

letx={};

x.a=true;

Thetypeofxisinferredas{},anemptyobject.Thus,theassignmenttox.aisnotallowed,sincethepropertyadoesnotexist.Youcanfixthisbyaddingatypetothedefinition:letx:{a?:boolean}={}.

classBase{

a:boolean;

}

classDerivedextendsBase{

b:string;

}

letx=newDerived();

x=newBase();

Inthiscase,thetypeofxisDerived.Theassignmentonthelastlineisnotallowed,sinceBaseisnotassignabletoDerived.Youcanagainfixthisbyaddingatype:letx:Base=newDerived().

Ifthetypeofavariableisunknownorverycomplex,youcanuseanyasthetypeforthevariable,whichdisablestypecheckingforthatvariable.

Otherpossiblesourcesoferrorsareclasses.Whenyouusetheclasskeywordtocreateclasses,youcangeterrorsthatapropertydoesnotexistintheclass.

classA{

constructor(){

this.x=true;

}

}

Inthisexample,youwouldgetanerrorthatthepropertyxdoesnotexistinA.InTypeScript,youmustdeclareallpropertiesofaclass.

classA{

x:boolean;

constructor(){

this.x=true;

}

}

Page 1045: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

MosterrorsofTypeScriptshouldnowbefixed.Somecaseshowevercanstillgeneratetypeerrors,forexamplewhenavariablehasdifferenttypes,whichisdiscussedinthenextsession.

Page 1046: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingtypeguardsandcastsAcommonpatterninJavaScriptisthatafunctionacceptseitherasinglevalueofacertaintype,oranarrayofmultipletypes.Youcanexpresssuchatypewithauniontype:

functionfoo(input:string|string[]){

...

}

Inthebodyofsuchafunction,youwouldcheckiftheargumentisanarrayorasinglestring.Inmostcases,TypeScriptcancorrectlyfollowthispattern.Forinstance,TypeScriptcanchangethetypeofinputinthenextexample.

functionfoo(input:string|string[]){

if(typeofinput==="string"){

}else{

}

}

Thetypeofinputisstringintheblockaftertheifandstring[]intheelse-block.Thechangingofatypeiscallednarrowingandthechecksforatypearecalledtypeguards.TypeScripthasbuilt-insupportfortypeofandinstanceoftypeguards,butyoucanalsodefineyourown.Auserdefinedtypeguardfunctionisafunctionthattakesthevalueasoneofitsargumentsandreturnstruewhenthevalueisofacertaintype.Atypeguardcanbewrittenlikethis:

functionisBar(value:Foo):valueisBar{

...

}

Asyoucansee,thereturntypeofisBarisvalueisBar,aspecialbooleantype.Ifyouhaveafunctionthatchecksthatavalueisofacertaintype,youshouldaddareturntypetomakeitatypeguard.

IftheTypeScriptcompilerstillcannotcorrectlynarrowthetypeofavariableonacertainlocation,youmustaddacast.Acastisacompilerinstructioninwhichtheprogrammerguaranteesthatavalueisofacertaintype.Atypeguardcanbewrittenintwodifferentways.

<Bar>value

valueasBar

Thefirstsyntaxismostused,butnotsupportedinJSXorTSXfiles.InaTSXfile,youmustusethesecondsyntax.

Whenyouhavefixedalltheseerrors,theprojectshouldcompilewithouterrorsagain.

Page 1047: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

UsingmodernsyntaxTheclasskeywordwasintroducedinES2015,arecentversionofJavaScript.Olderprojectscreatedclasseswithafunctionandprototypesshouldmigratetothenewclasssyntax.InTypeScript,theseclassescanbetypedbetter.Followingaretwocodefragments,whichshowthesameclasswrittenwiththeprototypeandwiththeclasssyntax.

varA=(function(){

functionA(){

this.x=true;

}

A.prototype.a=function(){

};

returnA;

}());

classA{

x:boolean;

constructor(){

this.x=true;

}

a(){

}

}

Youcanalsousethenew,blockscopedvariabledeclarations.Insteadofvarxyoushouldwriteconstxforavariablethatisnotreassignedandletxforavariablethatwillbereassigned.

Finally,youcanalsousearrowfunctions(orlambdaexpressions).Usingnormalfunctiondefinitions,thevalueofthisislostincallbacks.Thus,youhadtostorethatinavariable(selfor_thiswascommonlyused).Youcanreplacethatwithanarrowfunction.

var_this=this;

functionmyCallback(){

_this...

}

Thiscodefragmentcanberewrittento:

constmyCallback=()=>{

this...

};

Page 1048: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AddingtypesThefilecompilesnow,butlotsofvariablesandparametersmightbetypedasany.Forcomplextypes,youcanfirstcreateaninterface,forobjecttypes,oratypealias,forfunctiontypesoruniontypes.

TypeScriptdoesnotinfertypesinthefollowingcases:

Variabledeclarationwithoutaninitializer(likevarx;)ParametersofafunctiondefinitionwithoutadefaultvalueReturntypeofafunctionthatusesrecursion

InaneditorlikeVSCode,youcancheckthetypeofavariable,parameterorfunctionbyhoveringoverit.Ontheselocationsyoushouldaddatypeannotationyourself.

Page 1049: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

RefactoringtheprojectWhenyouhaveportedtheprojecttoTypeScript,youcanrefactortheprogrammoreeasily.YoushouldremovepatternsthatdonotfitwellwithTypeScript.Forinstance,magicstringvaluesshouldbereplacedbyenums.Whenyouhaveaprojectthatusesaframework,youcanalsodosomeframeworkrelatedrefactoring.ForaReactproject,youmightwanttoupgradefromtheoldclasscreationwithReact.createClasstothenewclasssyntax.

Apropereditorcanhelpduringrefactoring.VSCodecanrenameanidentifierinthewholeprojectorfindallreferencesofanidentifier.Itcanalsoformatyourcodeifit'smessyorjumptothedefinitionofanidentifier.Youcanaccesstheseoptionswitharight-clickontheidentifierinthecode.

Whichstepsyoumustdoforrefactoringdependsonyourproject.Youshouldlookforpartsofthecodethatarenottypedorincorrectlytyped,becauseofabadstructureorsomedynamicbehavior.

Page 1050: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

EnablestrictchecksYoucanenablestricterchecksinTypeScript.Thesecheckscanimprovethequalityofyourprogram.Hereareafewoptionsthatcanbeuseful.

noImplicitAny:Checksthatnovariablesaretypedasanyunlessyouexplicitlyannotatedthemwithany.noImplicitReturns:Checksthatallexecutionpathsofafunctionreturnavalue.strictNullChecks:Enablesstrictchecksforvariablesthatmightbeundefinedornull.

Page 1051: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

SummaryInthischapter,wehavelookedatvariousstepsinvolvedinmigratingaprojectfromJavaScripttoTypeScript.Wesawhowaprojectcanbemigratedgradually.WelookedatvariouswaystoupdateanoldprojectsothatitcanusenewJavaScriptfeaturesandhowitcanusethetypesystemofTypeScript.Youcanuseyourknowledgefromthepreviouschapterstomaketheprojectevenbetter.

Page 1052: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

AppendixA.BibliographyThislearningpathhasbeenpreparedforyoutobuildstunningapplicationsbyleveragingthefeaturesofTypeScript.ItcomprisesofthefollowingPacktproducts:

LearningTypeScript,RemoH.JansenTypeScriptDesignPatterns,VilicVaneTypeScriptBlueprints,IvoGabedeWolff

Page 1053: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

IndexA

action/Dataflowafter()function/Creatingtestassertions,specs,andsuiteswithMochaandChaiaggregation

about/Association,aggregation,andcompositionAkamai

URL/NetworkperformanceanduserexperienceAMDmodules

about/AMDmodules–runtimeonlyannotations

anddecorators/Annotationsanddecoratorsabout/Annotationsanddecoratorsclassdecorators/Theclassdecoratorsmethoddecorators/Themethoddecoratorspropertydecorators/Thepropertydecoratorsparameterdecorators/Theparameterdecoratorsdecoratorfactory/Thedecoratorfactorydecoratorswitharguments/DecoratorswithargumentsreflectionmetadataAPI/ThereflectionmetadataAPI

Anytype/Variables,basictypes,andoperatorsapplication/Applicationapplicationevents/WritinganMVCframeworkfromscratch,Applicationeventsapplicationprogramminginterface(API)/Theinterfacesegregationprincipleapplymethod/Thecall,apply,andbindmethodsapplyMixinsmethod/Mixinsarchitecture,single-pageapplication(SPA)

about/Theapplication'sarchitectureMarketControllercontroller,implementing/Theapplication'sarchitecture

arithmeticoperatorsabout/Arithmeticoperators+/Arithmeticoperators-/Arithmeticoperators*/Arithmeticoperators

arrowfunction/Arrowfunctionsassertion/Assertionsassignmentoperators

about/Assignmentoperators=/Assignmentoperators+=/Assignmentoperators-=/Assignmentoperators*=/Assignmentoperators

Page 1054: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

/=/Assignmentoperators%=/Assignmentoperators

associationabout/Association,aggregation,andcomposition

asynchronouscodetesting/Testingtheasynchronouscode

asynchronousflowcontrol,typesconcurrent/Promisesseries/Promiseswaterfall/Promisescomposite/Promises

asynchronousfunctionsasync/Asynchronousfunctions–asyncandawaitabout/Asynchronousfunctions–asyncandawaitawait/Asynchronousfunctions–asyncandawait

asynchronousprogrammingabout/AsynchronousprogramminginTypeScriptcallbacks/Callbacksandhigher-orderfunctionshigherorderfunctions/Callbacksandhigher-orderfunctionsarrowfunctions/Arrowfunctionscallbackhell/Callbackhellpromises/Promisesgenerators/Generatorsasynchronousfunctions/Asynchronousfunctions–asyncandawait

Atomabout/AtomURL/Atom

Page 1055: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

BBDD

about/Behavior-drivendevelopment(BDD)URL/Behavior-drivendevelopment(BDD)

before()function/Creatingtestassertions,specs,andsuiteswithMochaandChaibeforeEach()function/Creatingtestassertions,specs,andsuiteswithMochaandChai,Spiesbindmethod/Thecall,apply,andbindmethodsBitBucket

URL/Sourcecontroltoolsbitwiseoperators

about/Bitwiseoperators&/Bitwiseoperators|/Bitwiseoperators^/Bitwiseoperators~/Bitwiseoperators<</Bitwiseoperators>>/Bitwiseoperators>>>/Bitwiseoperators

BOM(BrowserObjectModel)/AmbientdeclarationsBower

about/BowerURL/Bower

browserifiedfunction/OptimizingaTypeScriptapplicationBrowserify

URL/CommonJSmodules–runtimeonly,UMDmodules–runtimeonlyBrowserObjectModel(BOM)/TheenvironmentBrowserSync

URL/Synchronizedcross-devicetesting

Page 1056: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Ccallback/Callbacksandhigher-orderfunctionscallbackhell/Callbackhellcallmethod/Thecall,apply,andbindmethodsCentralProcessingUnit(CPU)/PerformanceandresourcesChai

about/Chaiused,forcreatingspecs/Creatingtestassertions,specs,andsuiteswithMochaandChaiused,forcreatingtestassertions/Creatingtestassertions,specs,andsuiteswithMochaandChaiused,forcreatingsuites/Creatingtestassertions,specs,andsuiteswithMochaandChaiURL/AssertingexceptionsTDD,versusBDD/TDDversusBDDwithMochaandChai

Chaplin/Controllerschartmodel,single-pageapplication(SPA)

implementing/Implementingthechartmodelchartview,single-pageapplication(SPA)

implementing/Implementingthechartviewcircular1.ts/Circulardependenciescircular2.ts/Circulardependenciescirculardependency

about/CirculardependenciesURL/Circulardependencies

class/Classesclassdecorators/Theclassdecoratorsclient-siderendering/Thesingle-pageapplicationarchitectureclosures

about/Closuresstaticvariables,usingwith/Staticvariableswithclosuresprivatemembers,usingwith/Privatememberswithclosures

collections/Collectionscollectionviews/CollectionviewsCommonJSmodules

runtime/CommonJSmodules–runtimeonlycomparisonoperators

about/Comparisonoperators==/Comparisonoperators===/Comparisonoperators!=/Comparisonoperators>/Comparisonoperators</Comparisonoperators

Page 1057: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

>=/Comparisonoperators<=/Comparisonoperators

complextypeserialization/ThereflectionmetadataAPIcomponents,TypeScript

language/TypeScriptcomponentscompiler/TypeScriptcomponentslanguageservices/TypeScriptcomponentsIDEintegration/TypeScriptcomponents

compositionabout/Association,aggregation,andcomposition,Composition

configurationtesting/Performancetestingautomationconstructor/ClassesContinuousIntegration(CI)tools

about/ContinuousIntegrationtoolscontroller/Controllers,WritinganMVCframeworkfromscratch,ControllercreateElementproperty/Specializedoverloadingsignatures

Page 1058: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Ddata,single-pageapplication(SPA)

about/Theapplication'sdatamarketdata/Theapplication'sdatastockquotedata/Theapplication'sdatachartdata/Theapplication'sdata

datatypesBoolean/Variables,basictypes,andoperatorsnumber/Variables,basictypes,andoperatorsstring/Variables,basictypes,andoperatorsarray/Variables,basictypes,andoperatorsenum/Variables,basictypes,andoperatorsany/Variables,basictypes,andoperatorsvoid/Variables,basictypes,andoperators

declarationfiles/tsddecoratorfactory/Thedecoratorfactorydecorators

prerequisites/Prerequisitesandannotations/Annotationsanddecoratorswitharguments/Decoratorswitharguments

DefinitelyTypedabout/tsdURL/tsd

dependencyinversion(DI)principleabout/ThedependencyinversionprincipleURL/Thedependencyinversionprinciple

depthoftheinheritancetree(DIT)/Inheritancedesigntimecode/Designgoalsdevelopmentworkflow

about/Amoderndevelopmentworkflowprerequisites/Prerequisitespackagemanagementtools/Packagemanagementtoolstaskrunners/Taskrunnerstestrunner/Testrunnerssynchronizedcross-devicetesting/Synchronizedcross-devicetestingContinuousIntegration(CI)tools/ContinuousIntegrationtoolsscaffoldingtools/Scaffoldingtools

diamondproblem/Mixinsdispatcher/Dispatcher,WritinganMVCframeworkfromscratch,Dispatcherdo-whileexpression/Theexpressionistestedatthebottomoftheloop(do…while)DocumentObjectModel(DOM)/tsd,TheenvironmentDOM(DocumentObjectModel)/Ambientdeclarationsdon’trepeatyourself(DRY)/Generics

Page 1059: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

double-selectionstructure(if…else)/Thedouble-selectionstructure(if…else)dummy/Dummies

Page 1060: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Eend-to-end(E2E)test

about/SeleniumandNightwatch.jsrunning,withSelenium/RunningE2EtestswithSeleniumandNightwatch.jsrunning,withNightwatch.js/RunningE2EtestswithSeleniumandNightwatch.jscreating,withNightwatch.js/Creatingend-to-endtestswithNightwatch.js

Errorclass/TheErrorclassES6modules

designtime/ES6modules–runtimeanddesigntimeruntime/ES6modules–runtimeanddesigntime

eventemitter/Eventemittereventloop

about/Theruntime,Theeventloopframes/Framesstack/Stackqueue/Queueheap/Heap

eventsabout/Eventsuserevents/Eventsapplicationevents/Events

Exceptionclass/TheErrorclassexceptionhandling

about/ExceptionhandlingErrorclass/TheErrorclasstry…catchstatements/Thetry…catchstatementsandthrowstatementsthrowstatements/Thetry…catchstatementsandthrowstatements

exceptionsasserting/Assertingexceptions

executiontimecode/Designgoalsexternalmodules

about/Externalmodules–designtimeonlydesigntime/Externalmodules–designtimeonly

Page 1061: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Ffilestructure,single-pageapplication(SPA)

about/Theapplication'sfilestructure,Configuringtheautomatedbuildflowcontrolstatements

about/Flowcontrolstatementssingle-selectionstructure(if)/Thesingle-selectionstructure(if)double-selectionstructure(if…else)/Thedouble-selectionstructure(if…else)inlineternaryoperator(?)/Theinlineternaryoperator(?)multiple-selectionstructure(switch)/Themultiple-selectionstructure(switch)whileexpression/Theexpressionistestedatthetopoftheloop(while)do-whileexpression/Theexpressionistestedatthebottomoftheloop(do…while)for-instatement/Iterateoneachobject'sproperties(for…in)forstatement/Countercontrolledrepetition(for)

foofunction/Framesfor-instatement/Iterateoneachobject'sproperties(for…in)forstatement/Countercontrolledrepetition(for)frames/Framesframespersecond(FPS)/Framespersecond(FPS)framework/Frameworkfunctions,TypeScript

workingwith/WorkingwithfunctionsinTypeScriptdeclaring/Functiondeclarationsandfunctionexpressionsexpressions/Functiondeclarationsandfunctionexpressionstypes/Functiontypeswithoptionalparameters/Functionswithoptionalparameterswithdefaultparameters/Functionswithdefaultparameterswithrestparameters/FunctionswithrestparametersURL/Functionswithrestparametersoverloading/Functionoverloadingspecializedoverloadingsignature/Specializedoverloadingsignaturesfunctionscope/Functionscopeimmediatelyfunctions/Immediatelyinvokedfunctionsgenerics/Genericstagfunctions/Tagfunctionsandtaggedtemplatestaggedtemplates/Tagfunctionsandtaggedtemplates

Page 1062: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Ggarbagecollection/Functionscopegenerators

URL/Scaffoldingtools/Generatorsgenericclasses

about/Genericclassesgenericconstraints

about/Genericconstraintsmultipletypes/Multipletypesingenerictypeconstraints

Gitabout/GitandGitHubURL/GitandGitHub,Sourcecontroltools

GitHubabout/GitandGitHubURL/GitandGitHub,Framespersecond(FPS)

GoogleAnalyticsURL/Performancemonitoringautomation

GooglePageSpeedInsightsURL/Networkperformancebestpracticesandrules

GPUperformanceanalysisabout/GPUperformanceanalysisframespersecond(FPS)/Framespersecond(FPS)

GraphicsProcessorUnit(GPU)/PerformanceandresourcesGrunt

URL/TaskrunnersGulp

tasksexecutionorder,managing/ManagingtheGulptasks'executionorderURL/ManagingtheGulptasks'executionorderabout/Gulpused,forbuildingapplication/BuildingtheapplicationwithGulp

gulp-typescriptdocumentationURL/CompilingtheTypeScriptcode

gulpsrcfunction/CheckingthequalityoftheTypeScriptcode

Page 1063: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Hhandlebars

URL/CallbackhellHardDiskDrive(HDD)/PerformanceandresourcesHARviewer

URL/Performancemonitoringautomationhash(#)navigation

about/Routerandhash(#)navigationheap/Heaphigherorderfunctions/Callbacksandhigher-orderfunctionsHTTPArchive(HAR)/Performancemonitoringautomation

Page 1064: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Iimmediatelyinvokedfunctionexpression(IIFE)/Immediatelyinvokedfunctionsimmediatelyinvokedfunctionexpression(IIFE)/Prototypesindependent(free)variables/Closuresinheritance

about/Inheritancemixins/Mixins

initializemethod/Application,Implementingthemarketcontrollerinlineternaryoperator(?)/Theinlineternaryoperator(?)instanceofoperator/Theclassdecoratorsinstanceproperties

versusclassproperties/Instancepropertiesversusclasspropertiesabout/Instancepropertiesversusclassproperties

interfaces/Interfaces,Interfacesinterfacesegregationprinciple(ISP)/TheinterfacesegregationprincipleInversionofControl(IoC)/ThedependencyinversionprincipleIstanbul

URL/Testcoverageabout/Istanbul

itemviews/Itemviews

Page 1065: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

JJavadocumentation

URL/SeleniumandNightwatch.jsJQuery/npm

Page 1066: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

KKarma

about/Karmaused,forrunningunittest/RunningtheunittestwithKarmaURL/RunningtheunittestwithKarma

karmaconfigurationdocumentation,URL/Testrunners

Page 1067: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Llanguagefeatures

about/TypeScriptlanguagefeaturestypes/Typesvariables/Variables,basictypes,andoperatorsvar/Var,let,andconstlet/Var,let,andconstconst/Var,let,andconstuniontypes/Uniontypestypeguards/Typeguardstypealiases/Typealiasesambientdeclarations/Ambientdeclarationsarithmeticoperators/Arithmeticoperatorscomparisonoperators/Comparisonoperatorsflowcontrolstatements/Flowcontrolstatementsfunctions/Functionsclasses/Classesinterfaces/Interfacesmodules/Namespaces

LastInFirstOut(LIFO)/Stacklayout,single-pageapplication(SPA)

about/Theapplication'slayoutLiskovsubstitutionprinciple(LSP)/TheLiskovsubstitutionprincipleloadtesting/Performancetestingautomationlogicaloperators

about/Logicaloperators&&/Logicaloperators||/Logicaloperators!/Logicaloperators

Page 1068: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Mmark/Thegarbagecollectormark-and-sweepalgorithm/Thegarbagecollectormarketcontroller,single-pageapplication(SPA)

implementing/Implementingthemarketcontrollermarkettemplate,single-pageapplication(SPA)

implementing/Implementingthemarkettemplatemarketview,single-pageapplication(SPA)

implementing/ImplementingthemarketviewMarkit

about/Theapplication'sdataURL/Theapplication'sdata

mediator/Mediator,WritinganMVCframeworkfromscratch,Mediatormemoryleak

about/Memoryperformanceanalysisissues,preventing/Thegarbagecollector

methoddecoratorsabout/Themethoddecoratorsinvoking/Themethoddecorators

methodoverriding/Inheritancemixin/Mixins

about/MixinsMocha

about/Mochaused,forcreatingtestassertions/Creatingtestassertions,specs,andsuiteswithMochaandChaiused,forcreatingspecs/Creatingtestassertions,specs,andsuiteswithMochaandChaiused,forcreatingsuites/Creatingtestassertions,specs,andsuiteswithMochaandChaiTDD,versusBDD/TDDversusBDDwithMochaandChai

mocksabout/Mocks

modelabout/Models,WritinganMVCframeworkfromscratch,Modelandmodelsettingssettings/Modelandmodelsettings

Model-View-Controller(MVC)/TheMV*architectureModel-View-Presenter(MVP)/TheMV*architectureModel-View-ViewModel(MVVM)/TheMV*architectureModernizr

about/TheenvironmentURL/Theenvironment

Page 1069: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

moduleloaderabout/ModulesRequireJS/ModulesBrowserify/ModulesSystemJS/Modules

modules/Namespacesabout/ModulesES6modules/ES6modules–runtimeanddesigntimeexternalmodules/Externalmodules–designtimeonlyAMDmodules/AMDmodules–runtimeonlyCommonJSmodules/CommonJSmodules–runtimeonlyUMDmodules/UMDmodules–runtimeonlySystemJSmodules/SystemJSmodules–runtimeonly

multiple-selectionstructure(switch)/Themultiple-selectionstructure(switch)multipleinheritance/MixinsMV*architecture

about/TheMV*architectureMV*frameworks

components/CommoncomponentsandfeaturesintheMV*frameworksfeatures/CommoncomponentsandfeaturesintheMV*frameworksmodel/Modelscollections/Collectionsitemviews/Itemviewscollectionviews/Collectionviewscontrollers/Controllersevents/Eventsrouter/Routerandhash(#)navigationhash(#)navigation/Routerandhash(#)navigationmediator/Mediatordispatcher/Dispatcherclient-siderendering/Client-siderenderingandVirtualDOMVirtualDOM/Client-siderenderingandVirtualDOMuserinterface(UI)databinding/Userinterfacedatabindingdataflow/Dataflowwebcomponents/WebcomponentsandshadowDOMshadowDOM/WebcomponentsandshadowDOMapplicationframework,selecting/Choosinganapplicationframework

MVCframeworkwriting,fromscratch/WritinganMVCframeworkfromscratchprerequisites/Prerequisites

MVCframeworkcomponentsapplication/WritinganMVCframeworkfromscratch,Applicationmediator/WritinganMVCframeworkfromscratch,Mediatorapplicationevents/WritinganMVCframeworkfromscratch,Applicationevents

Page 1070: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

router/WritinganMVCframeworkfromscratch,Routerroutes/WritinganMVCframeworkfromscratch,Routecontrollers/WritinganMVCframeworkfromscratchmodels/WritinganMVCframeworkfromscratch,Modelandmodelsettingsviews/WritinganMVCframeworkfromscratcheventemitter/Eventemitterdispatcher/Dispatchercontroller/Controllermodelsettings/Modelandmodelsettingsviewsettings/Viewandviewsettingsview/Viewandviewsettingsframework/Framework

Page 1071: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

N@namedecorator/ThereflectionmetadataAPInamespaces

about/NamespacesNASDAQmodel,single-pageapplication(SPA)

implementing/ImplementingtheNASDAQmodelNationalAssociationofSecuritiesDealersAutomatedQuotations(NASDAQ)/Theapplication'srequirementsnetsniff.jsfile

URL/Performancemonitoringautomationnetworkperformance

anduserexperience/Networkperformanceanduserexperiencebestpracticesandrules/Networkperformancebestpracticesandrules

NewYorkstockexchange(NYSE)/Theapplication'srequirementsnext()function/GeneratorsNightwatch.js

about/SeleniumandNightwatch.jsused,forrunningend-to-end(E2E)test/RunningE2EtestswithSeleniumandNightwatch.jsURL/RunningE2EtestswithSeleniumandNightwatch.jsused,forcreatingend-to-endtests/Creatingend-to-endtestswithNightwatch.js

Node.jsURL/TypeScriptlanguagefeatures,Node.jsabout/Node.js

nodepackagemanager(npm)/Prerequisitesnpm

about/npmURL/npm

npminitcommand/npmNYSEmodel,single-pageapplication(SPA)

implementing/ImplementingtheNYSEmodel

Page 1072: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Oobject-orientedprogramming(OOP)/SOLIDprinciplesObject.definePropertyfunction/ThepropertydecoratorsObject.getOwnPropertyDescriptor()method/Themethoddecoratorsobjectprototype,accessing

person.prototype/Accessingtheprototypeofanobjectperson.getPrototypeOf(person)/Accessingtheprototypeofanobjectperson.__proto__/Accessingtheprototypeofanobject

onSubmit()function/Spiesoptionalstatictypenotation/Optionalstatictypenotation

Page 1073: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

P@parameterTypesdecorator/ThereflectionmetadataAPIpackage.jsonconfiguration

URL/npmpackagemanagementtools

about/Packagemanagementtoolsnpm/npmBower/Bowertsd/tsd

parameterdecorators/Theparameterdecoratorsperformance-bookmarklet

about/NetworkperformanceanalysisURL/Networkperformanceanalysis

performanceanalysisabout/Performanceanalysisnetworkperformance/NetworkperformanceanalysistimingAPIdatapoints/Networkperformanceanalysisuserexperience/NetworkperformanceanduserexperienceGPUperformanceanalysis/GPUperformanceanalysisCPUperformanceanalysis/CPUperformanceanalysismemoryperformanceanalysis/Memoryperformanceanalysisgarbagecollector/Thegarbagecollector

performanceautomationabout/Performanceautomationoptimizationautomation/Performanceoptimizationautomationmonitoringautomation/Performancemonitoringautomationtestingautomation/Performancetestingautomation

performancemetricsabout/Performancemetricsavailability/Availabilityresponsetime/Theresponsetimeprocessingspeed(clockrate)/Processingspeedlatency/Latencybandwidth/Bandwidthscalability/Scalability

performancemonitoringautomationabout/Performancemonitoringautomationrealusermonitoring(RUM)/Performancemonitoringautomationsimulatedbrowsers/Performancemonitoringautomationreal-browsermonitoring/Performancemonitoringautomation

performancetestingautomationabout/Performancetestingautomationloadtesting/Performancetestingautomation

Page 1074: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

stresstesting/Performancetestingautomationsoaktesting/Performancetestingautomationspiketesting/Performancetestingautomationconfigurationtesting/Performancetestingautomation

Personclass/ClassesPhantomJS/Performancemonitoringautomation

URL/Performancemonitoringautomationabout/PhantomJS

prerequisites,applicationtestingabout/PrerequisitesGulp/GulpKarma/KarmaIstanbul/IstanbulMocha/MochaChai/ChaiSinon.JS/Sinon.JStypedefinitions/TypedefinitionsPhantomJS/PhantomJSSelenium/SeleniumandNightwatch.jsNightwatch.js/SeleniumandNightwatch.js

prerequisites,decoratorsabout/Prerequisites

prerequisites,developmentworkflowabout/PrerequisitesNode.js/Node.jsAtom/AtomGit/GitandGitHubGitHub/GitandGitHub

prerequisites,single-pagewebapplicationabout/Prerequisites

privatemembersusing,withclosures/Privatememberswithclosures

promisesabout/Promisespendingstate/Promisesfulfilledstate/Promisesrejectedstate/Promises

propertydecorators/Thepropertydecoratorspropertyshadowing/Theprototypechainprototypes

about/Prototypesinstanceproperties,versusclassproperties/Instancepropertiesversusclasspropertiesinheritance/Prototypalinheritance

Page 1075: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

chain/Theprototypechainofobject,accessing/Accessingtheprototypeofanobject

pub/sub/Mediatorpublish/subscribedesignpattern

publishmethod/Mediatorsubscribemethod/Mediatorunsubscribe/Mediator

Page 1076: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

QQ(version1.0.1)

URL/Promisesqueue/Queue

Page 1077: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

R@returnTypedecorator/ThereflectionmetadataAPIRandomAccessMemory(RAM)/PerformanceandresourcesReflect.getMetadata()function/ThereflectionmetadataAPIreflectionmetadataAPI/ThereflectionmetadataAPIrendermethod/SpiesrequestAsyncmethod/ModelandmodelsettingsRequireJS

URL/AMDmodules–runtimeonlyrequisites,single-pagewebapplication/Theapplication'srequirementsresources

CentralProcessingUnit(CPU)/PerformanceandresourcesGraphicsProcessorUnit(GPU)/PerformanceandresourcesRandomAccessMemory(RAM)/PerformanceandresourcesHardDiskDrive(HDD)/PerformanceandresourcesSolidStateDrive(SSD)/Performanceandresourcesnetworkthroughput/Performanceandresources

resourcetimingURL/Networkperformanceanalysis

responsetimeabout/Theresponsetimewaittime/Theresponsetimeservicetime/Theresponsetimetransmissiontime/Theresponsetime

rootcomponent,single-pageapplication(SPA)implementing/Implementingtherootcomponent

routerabout/Thesingle-pageapplicationarchitecture,Routerandhash(#)navigation,WritinganMVCframeworkfromscratch,Router

routes/Routeruntimecode/Designgoalsruntimeenvironment

about/Theenvironment

Page 1078: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Sscaffoldingtool

about/ScaffoldingtoolsSelenium

about/SeleniumandNightwatch.jsused,forrunningend-to-end(E2E)test/RunningE2EtestswithSeleniumandNightwatch.js

serializemethod/RouteshadowDOM/WebcomponentsandshadowDOMshooterfunction/Closuressingle-pagewebapplication

prerequisites/Prerequisitesrequisites/Theapplication'srequirementsdata/Theapplication'sdataarchitecture/Theapplication'sarchitecturefilestructure/Theapplication'sfilestructureautomatedbuild,configuring/Configuringtheautomatedbuildlayout/Theapplication'slayoutrootcomponent,implementing/Implementingtherootcomponentmarketcontroller,implementing/ImplementingthemarketcontrollerNASDAQmodel,implementing/ImplementingtheNASDAQmodelNYSEmodel,implementing/ImplementingtheNYSEmodelmarketview,implementing/Implementingthemarketviewmarkettemplate,implementing/Implementingthemarkettemplatesymbolcontroller,implementing/Implementingthesymbolcontrollersymbolview,implementing/Implementingthesymbolviewchartmodel,implementing/Implementingthechartmodelchartview,implementing/Implementingthechartviewtesting/Testingtheapplicationpreparing,forproductionrelease/Preparingtheapplicationforaproductionrelease

single-selectionstructure(if)/Thesingle-selectionstructure(if)Sinon.JS

about/Sinon.JSused,fortestingspies/TestspiesandstubswithSinon.JS,Spiesused,fortestingstubs/TestspiesandstubswithSinon.JS,StubsURL/Spies

soaktesting/Performancetestingautomationsoftwaretesting

about/Softwaretestingglossaryassertion/Assertionsspecs/Specstestcase/Testcases

Page 1079: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

suites/Suitesspies/Spiesdummy/Dummiesstub/Stubsmocks/Mockstestcoverage/Testcoverage

SOLIDprinciplesabout/SOLIDprinciples,ApplyingtheSOLIDprinciplessingleresponsibilityprinciple(SRP)/SOLIDprinciplesopen/closedprinciple(OCP)/SOLIDprinciplesLiskovsubstitutionprinciple(LSP)/SOLIDprinciples,TheLiskovsubstitutionprincipleinterfacesegregationprinciple(ISP)/SOLIDprinciples,Theinterfacesegregationprincipledependencyinversion(DI)principle/SOLIDprinciples,Thedependencyinversionprinciple

SolidStateDrive(SSD)/Performanceandresourcessourcecontroltools

about/SourcecontroltoolsSPA

architecture/Thesingle-pageapplicationarchitecturespecs

about/Specscreating,withMono/Creatingtestassertions,specs,andsuiteswithMochaandChaicreating,withChai/Creatingtestassertions,specs,andsuiteswithMochaandChai

spiesabout/Spiestesting,withSinon.JS/TestspiesandstubswithSinon.JS,Spies

spiketesting/Performancetestingautomationstack/Stackstaticvariables

using,withclosures/Staticvariableswithclosuresstresstesting/Performancetestingautomationstubs

about/Stubstesting,withSinon.JS/TestspiesandstubswithSinon.JS,Stubs

suitesabout/Suitescreating,withMono/Creatingtestassertions,specs,andsuiteswithMochaandChaicreating,withChai/Creatingtestassertions,specs,andsuiteswithMochaandChai

sweep/Thegarbagecollectorsymbolcontroller,single-pageapplication(SPA)

Page 1080: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

implementing/Implementingthesymbolcontrollerquotemodel,implementing/Implementingthequotemodel

symbolview,single-pageapplication(SPA)implementing/Implementingthesymbolview

synchronizedcross-devicetestingabout/Synchronizedcross-devicetesting

SystemJSmodulesabout/SystemJSmodules–runtimeonlyURL/SystemJSmodules–runtimeonlymistakes,URL/SystemJSmodules–runtimeonly

Page 1081: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

T@typedecorator/ThereflectionmetadataAPItagfunction/Tagfunctionsandtaggedtemplatestaskrunners

about/TaskrunnersTypeScriptcodequality,checking/CheckingthequalityoftheTypeScriptcodeTypeScriptcode,compiling/CompilingtheTypeScriptcodeTypeScriptapplication,optimizing/OptimizingaTypeScriptapplicationGulptasksexecutionorder,managing/ManagingtheGulptasks'executionorder

TDDabout/Test-drivendevelopmentversusBDD,withChai/TDDversusBDDwithMochaandChai

TemplateStrings/Functionoverloadingtestassertions

creating,withMocha/Creatingtestassertions,specs,andsuiteswithMochaandChaicreating,withChai/Creatingtestassertions,specs,andsuiteswithMochaandChai

testcase/Testcasestestcoverage

about/Testcoveragetestcoveragereports

generating/Generatingtestcoveragereportstestinfrastructure

settingup/Settingupatestinfrastructureapplication,buildingwithGulp/BuildingtheapplicationwithGulpunittest,runningwithKarma/RunningtheunittestwithKarmaE2Etests,runningwithSelenium/RunningE2EtestswithSeleniumandNightwatch.jsE2Etests,runningwithNightwatch.js/RunningE2EtestswithSeleniumandNightwatch.js

testingplanningabout/TestingplanningandmethodologiesTDD/Test-drivendevelopmentBDD/Behavior-drivendevelopment(BDD)behavior-drivendevelopment(BDD)/Behavior-drivendevelopment(BDD)termtestplan/Testsplansandtesttypestesttypes/Testsplansandtesttypes

testrunnerabout/TestrunnersKarma/Testrunners

testtypesunittest/Testsplansandtesttypespartialintegrationtests/Testsplansandtesttypes

Page 1082: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

fullintegrationtests/Testsplansandtesttypesregressiontests/Testsplansandtesttypesperformance/loadtests/Testsplansandtesttypesend-to-end(E2E)tests/Testsplansandtesttypesuseracceptancetests(UAT)/Testsplansandtesttypes

thisoperatorabout/ThethisoperatorURL/Thethisoperatoringlobalcontext/Thethisoperatorintheglobalcontextinfunctioncontext/Thethisoperatorinafunctioncontextcallmethod/Thecall,apply,andbindmethodsapplymethod/Thecall,apply,andbindmethodsbindmethod/Thecall,apply,andbindmethods

TodoMVCURL/Choosinganapplicationframework

TraceEventProfilingToolURL/Framespersecond(FPS)

transpiler/TypeScriptcomponentsTravisCI

URL/ContinuousIntegrationtoolsconfigurationoptions,URL/ContinuousIntegrationtoolsdocumentation,URL/ContinuousIntegrationtools

try…catchstatement/Promisestypedefinitionfile/tsdtypedefinitions/Typedefinitionstypeguards/TypeguardsTypeinference/Optionalstatictypenotationtypes,languagefeatures

about/Typesoptionalstatictypenotation/Optionalstatictypenotation

TypeScriptarchitecture/TheTypeScriptarchitecturecomponents/TypeScriptcomponentsURL/TypeScriptcomponents,TypeScriptlanguagefeatureslanguagefeatures/TypeScriptlanguagefeaturesplugins,URL/TypeScriptlanguagefeaturesexample/Puttingeverythingtogetherfunctions,workingwith/WorkingwithfunctionsinTypeScriptasynchronousprogramming/AsynchronousprogramminginTypeScript

TypeScriptarchitectureabout/TheTypeScriptarchitecturedesigngoals/Designgoals

TypeScriptclasses/Classesabout/Classes

Page 1083: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

TypeScriptcodequality,checking/CheckingthequalityoftheTypeScriptcodecompiling/CompilingtheTypeScriptcode

TypeScriptDefinitions(tsd)/tsdTypeScriptextension

URL,fordownload/TypeScriptlanguagefeaturesTypeScriptfunctions/Functions

Page 1084: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Uuniversalmoduledefinition(UMD)

about/UMDmodules–runtimeonlyunsubscribeToEventsmethod/Eventemitteruserinterface(UI)databinding

about/Userinterfacedatabindingone-waydatabinding/One-waydatabindingtwo-waydatabinding/Two-waydatabinding

Page 1085: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

VvalidateEmailmethod/ClassesViewclass

containerproperty/Callbackhelltemplateproperty/Callbackhellserviceproperty/Callbackhell

viewsabout/WritinganMVCframeworkfromscratch,Viewandviewsettingssettings/Viewandviewsettings

VisualStudio(VS)/TypeScriptcomponentsURLs/Atom

Page 1086: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

Wwebcomponents/WebcomponentsandshadowDOMwebperformanceanalysis

prerequisites/Prerequisitesresources/Performanceandresources

webworkers/Theeventloopwhileexpression/Theexpressionistestedatthetopoftheloop(while)

Page 1087: TypeScript: Modern JavaScript Developmentsd.blackball.lv/library/TypeScript_-_Modern_JavaScript_Development_(2016).pdfNode.js Atom Git and GitHub Source control tools Package management

YYSlow

URL/Networkperformancebestpracticesandrules