Upload
others
View
1
Download
0
Embed Size (px)
Citation preview
SwitchingtoAngular2
TableofContents
SwitchingtoAngular2
Credits
Foreword
AbouttheAuthor
AbouttheReviewers
www.PacktPub.com
eBooks,discountoffers,andmore
Whysubscribe?
Preface
Whatthisbookcovers
Whatyouneedforthisbook
Whothisbookisfor
Conventions
Readerfeedback
Customersupport
Downloadingtheexamplecode
Errata
Piracy
Questions
1.GettingStartedwithAngular2
TheevolutionoftheWeb–timeforanewframework
TheevolutionofECMAScript
WebComponents
WebWorkers
LessonslearnedfromAngularJS1.xinthewild
Controllers
Scope
DependencyInjection
Server-siderendering
Applicationsthatscale
Templates
Changedetection
Summary
2.TheBuildingBlocksofanAngular2Application
AconceptualoverviewofAngular2
Changingdirectives
GettingtoknowAngular2components
Componentsinaction
ComponentsinAngular2
Pipes
Definingpipes
Changedetection
Classicalchangedetection
AngularJS1.xchangedetection
Inthezone.js
Simplifieddataflow
EnhancingAngularJS1.x’schangedetection
Understandingservices
Understandingthenewcomponent-basedrouter
Angular2routedefinitionsyntax
Summary
3.TypeScriptCrashCourse
IntroductiontoTypeScript
Compile-timetypechecking
BettersupportbytexteditorsandIDEs
There’sevenmoretoTypeScript
UsingTypeScript
InstallingTypeScriptwithnpm
RunningourfirstTypeScriptprogram
TypeScriptsyntaxandfeaturesintroducedbyES2015andES2016
ES2015arrowfunctions
UsingtheES2015andES2016classes
Definingvariableswithblockscope
Meta-programmingwithES2016decorators
Usingconfigurabledecorators
WritingmodularcodewithES2015
UsingtheES2015modulesyntax
Takingadvantageoftheimplicitasynchronousbehavior
Usingaliases
Importingallthemoduleexports
Defaultexports
ES2015moduleloader
ES2015andES2016recap
Takingadvantageofstatictyping
Usingexplicittypedefinitions
Thetypeany
UnderstandingthePrimitivetypes
TheEnumtypes
UnderstandingtheObjecttypes
TheArraytypes
TheFunctiontypes
Definingclasses
Usingaccessmodifiers
Defininginterfaces
Interfaceinheritance
Implementingmultipleinterfaces
FurtherexpressivenesswithTypeScriptdecorators
Writinggenericcodebyusingtypeparameters
Usinggenericfunctions
Havingmultipletypeparameters
WritinglessverbosecodewithTypeScript’stypeinference
Bestcommontype
Contextualtypeinference
Usingambienttypedefinitions
Usingpredefinedambienttypedefinitions
Customambienttypedefinitions
Definingts.dfiles
Summary
4.GettingStartedwithAngular2ComponentsandDirectives
TheHelloworld!applicationinAngular2
Settingupourenvironment
Installingourprojectrepository
PlayingwithAngular2andTypeScript
Diggingintotheindex
UsingAngular2directives
ThengFordirective
Improvedsemanticsofthedirectivessyntax
Declaringvariablesinsideatemplate
Usingsyntaxsugarintemplates
DefiningAngular2directives
Settingthedirective’sinputs
Understandingthedirective’sconstructor
Betterencapsulationofdirectives
UsingAngular2’sbuilt-indirectives
Introducingthecomponent’sviewencapsulation
Implementingthecomponent’scontrollers
Handlinguseractions
Usingadirectives’inputsandoutputs
Findingoutdirectives’inputsandoutputs
Definingthecomponent’sinputsandoutputs
Passinginputsandconsumingtheoutputs
Eventbubbling
Renamingtheinputsandoutputsofadirective
Analternativesyntaxtodefineinputsandoutputs
ExplainingAngular2’scontentprojection
BasiccontentprojectioninAngular2
Projectingmultiplecontentchunks
Nestingcomponents
UsingViewChildrenandContentChildren
ViewChildversusContentChild
Hookingintothecomponent’slifecycle
Theorderofexecution
DefininggenericviewswithTemplateRef
Understandingandenhancingthechangedetection
Theorderofexecutionofthechangedetectors
Changedetectionstrategies
PerformanceboostingwithimmutabledataandOnPush
UsingimmutabledatastructuresinAngular
Summary
5.DependencyInjectioninAngular2
WhydoIneedDependencyInjection?
DependencyInjectioninAngular2
BenefitsofDIinAngular2
Configuringaninjector
Dependencyresolutionwithgeneratedmetadata
Instantiatinganinjector
Introducingforwardreferences
Configuringproviders
Usingexistingproviders
Definingfactoriesforinstantiatingservices
Childinjectorsandvisibility
Buildingahierarchyofinjectors
Configuringdependencies
Usingthe@Selfdecorator
Skippingtheselfinjector
Havingoptionaldependencies
Usingmultiproviders
UsingDIwithcomponentsanddirectives
Introducingtheelementinjectors
Declaringprovidersfortheelementinjectors
ExploringDIwithcomponents
viewProvidersversusproviders
UsingAngular’sDIwithES5
Summary
6.WorkingwiththeAngular2RouterandForms
Developingthe“Codersrepository”application
ExploringtheAngular2router
Definingtherootcomponentandbootstrappingtheapplication
UsingPathLocationStrategy
Configuringrouteswith@RouteConfig
UsingrouterLinkandrouter-outlet
Lazy-loadingwithAsyncRoute
UsingAngular2forms
Developingtemplate-drivenforms
Diggingintothetemplate-drivenform’smarkup
Usingthebuilt-informvalidators
Definingcustomcontrolvalidators
UsingselectinputswithAngular
UsingtheNgFormdirective
Two-waydata-bindingwithAngular2
Storingtheformdata
Listingallthestoreddevelopers
Summary
7.ExplainingPipesandCommunicatingwithRESTfulServices
Developingmodel-drivenformsinAngular2
Usingcompositionofcontrolvalidators
ExploringtheHTTPmoduleofAngular
UsingAngular’sHTTPmodule
Definingparameterizedviews
Definingnestedroutes
Transformingdatawithpipes
Developingstatelesspipes
UsingAngular’sbuilt-inpipes
Developingstatefulpipes
Usingstatefulpipes
UsingAngular’sAsyncPipe
UsingAsyncPipewithobservables
Summary
8.DevelopmentExperienceandServer-SideRendering
RunningapplicationsinWebWorkers
WebWorkersandAngular2
BootstrappinganapplicationrunninginWebWorker
MigratinganapplicationtoWebWorker
MakinganapplicationcompatiblewithWebWorkers
Initialloadofasingle-pageapplication
InitialloadofaSPAwithserver-siderendering
Server-siderenderingwithAngular2
Enhancingourdevelopmentexperience
TexteditorsandIDEs
Hotreloading
HotreloadinginAngular2
Bootstrappingaprojectwithangular-cli
Usingangular-cli
Angular2quickstarters
Angular2seed
Angular2Webpackstarter
Summary
Index
SwitchingtoAngular2
SwitchingtoAngular2Copyright©2016PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:March2016
Productionreference:1220316
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78588-620-1
www.packtpub.com
CreditsAuthor
MinkoGechev
Reviewers
MiškoHevery
DanielLamb
CommissioningEditor
EdwardGordon
AcquisitionEditor
KirkD’costa
ContentDevelopmentEditor
ShwetaPant
TechnicalEditor
MohitaVyas
CopyEditor
AkshataLobo
ProjectCoordinator
KinjalBari
Proofreader
SafisEditing
Indexer
MariammalChettiyar
Graphics
DishaHaria
ProductionCoordinator
NileshMohite
CoverWork
NileshMohite
ForewordAngular2isstillAngular,justbetter.ItisstillbuiltonthesameprinciplesthatmadeyouloveAngularJS:aquickandpowerfulsolutiontobuildingSinglePageApplications.InAngular2,theapplicationsarefaster,morevisibletoSEOandmobile,andarecross-platformready.SowhilstAngular2hasimprovedmanyoftheconceptsoverAngularJS,thephilosophyremainstruetotheoriginalvision.
SwitchingtoAngular2isabookthatrecognizesthis.Minko’sbooksuccessfullyhelpsyoutoswitchyourthinkingfromAngularJS1.xtoAngular2.FromyourfirstinteractionswithAngular2tothelast,thecoreconceptsofAngulararemaintainedthroughout.ThisguidewillhelpyoutoswitchtoAngular’snewwayofdoingthings.Minkoguidesyouthroughthechangesandnewfeaturesthathavebeenintroduced—components,directives,TypeScript,thenewrouter,andeverythingelseyouneedtostartusingAngular2foryournextproject.
AsAngular2takesupthechallengesetbytoday’schangingwebdevelopmentlandscapeandbuildsonthelegacyofAngularJS,it’sincrediblyimportantfortheAngularcommunitythattherearehighqualitylearningmaterialssuchasMinko’sbooktohelpAngulardevelopersmakethatfirstswitchovertothefuture.
MiškoHevery
CreatorofAngularJSandAngular2
AbouttheAuthorMinkoGechevisasoftwareengineerwhostronglybelievesinopensourcesoftware.Hehasdevelopednumeroussuchprojects,includingAngularJS1.xandAngular2styleguides,angular2-seed,astaticcodeanalyzerforAngular2projects,aspect.js,angular-aop,andmanyothers.HerunstrainingcoursesinJavaScript,Angular,andotherwebtechnologies.
Minkolovestoexperimentwiththeoreticalconceptsfromcomputerscienceandapplytheminpractice.HehasspokenaboutAngularandsoftwaredevelopmentatworldwideconferencesandmeetups,includingng-vegas,AngularConnect,ITWeekendKiev,AngularJS-SF,andAngularBerlin.
IwanttothankMiškoHeveryforhisgreatcontributionsinsoftwareengineeringandthetechnicalreviewofthisbook.Hehelpedmeprovideasprecisecontentaspossible.Tomakethecodesamplesforthebookeasytorun,Iusedangular2-seed.CorecontributoroftheprojectisLudovicHénin,whohelpedmakeitmuchmorethananAngular2starter.IalsowanttothanktoDanielLamb,RadoslavKirovandTeroParviainenwhogavemeextremelyvaluablefeedback!.
Icouldn’tcompletethebookwithoutthededicatedworkofthePacktPublishingteam.
Finally,IwanttothanktheteamatGoogleforgivingusAngular.Theyareaconstantinspiration.
AbouttheReviewersMiškoHeveryisthecreatoroftheAngularJSframework.Hehasapassionformakingcomplexthingssimple.HecurrentlyworksatGoogle,buthaspreviouslyworkedatAdobe,SunMicrosystems,Intel,andXerox,wherehebecameanexpertinbuildingwebapplicationsinweb-relatedtechnologies,suchasJava,JavaScript,Flex,andActionScript.
DanielLambisaseniorsoftwaredevelopmentprofessionalandanauthorwhoenjoyssharingtheknowledgehehasgained,specializinginlarge-scalearchitectureandfrontendwebdevelopment.Hisworkoverthelast16yearshasenabledhundredsofmillionstoengageandinteractduringbillionsofvisits.
www.PacktPub.com
eBooks,discountoffers,andmoreDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<[email protected]>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www2.packtpub.com/books/subscription/packtlib
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt’sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt’sentirelibraryofbooks.
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
PrefaceAngularJSisaJavaScriptdevelopmentframeworkthatmakesbuildingwebapplicationseasier.Itisusedtodayinlarge-scale,high-trafficwebsitesthatstrugglewithunderperformanceandportabilityissues,aswellasSEOunfriendlinessandcomplexityatscale.Angular2changesthese.
Itisthemodernframeworkyouneedtobuildperformantandrobustwebapplications.SwitchingtoAngular2isthequickestwaytogettogripswithAngular2,anditwillhelpyoutransitionintothebravenewworldofAngular2.
Bytheendofthebook,you’llbereadytostartbuildingquickandefficientAngular2applicationsthattakeadvantageofallthenewfeaturesonoffer.
WhatthisbookcoversChapter1,GettingstartedwithAngular2,kicksoffourjourneyintotheworldofAngular2.Itdescribesthemainreasonsbehindthedesigndecisionsoftheframework.Wewilllookintothetwomaindriversbehindtheshapeoftheframework—thecurrentstateoftheWebandtheevolutionoffrontenddevelopment.
Chapter2,TheBuildingBlocksofanAngular2Application,givesanoverviewofthecoreconceptsintroducedbyAngular2.We’llexplorehowthefoundationalbuildingblocksforthedevelopmentofapplicationsprovidedbyAngularJS1.xdifferfromtheonesinthelastmajorversionoftheframework.
Chapter3,TypeScriptCrashCourse,explainsthatalthoughAngular2islanguageagnostic,Google’srecommendationistotakeadvantageofthestatictypingofTypeScript.Inthischapter,you’lllearnalltheessentialsyntaxyouneedtodevelopAngular2applicationsinTypeScript!
Chapter4,GettingStartedwithAngular2ComponentsandDirectives,describesthecorebuildingblocksfordevelopingtheuserinterfaceofourapplications—directivesandcomponents.Wewilldiveintoconceptssuchasviewencapsulation,contentprojection,inputsandoutputs,changedetectionstrategies,andmore.We’lldiscussadvancedtopicssuchastemplatereferencesandspeedingupourapplicationsusingimmutabledata.
Chapter5,DependencyInjectioninAngular2,coversoneofthemostpowerfulfeaturesintheframework,whichwasinitiallyintroducedbyAngularJS1.x:itsdependencyinjectionmechanism.Itallowsustowritemoremaintainable,testable,andunderstandablecode.Bytheendofthischapter,wewillknowhowtodefinethebusinesslogicinservicesandgluethemtogetherwiththeUIthroughtheDImechanism.Wewillalsolookintosomemoreadvancedconcepts,suchastheinjectorshierarchy,configuringproviders,andmore.
Chapter6,WorkingwiththeAngular2RouterandForms,exploresthenewmoduleformanagingformsintheprocessofdevelopingareal-lifeapplication.Wewillalsoimplementapagethatshowstheenteredthroughtheformdata.Intheend,wewillgluetheindividualpagestogetherintoanapplicationbyusingthecomponent-basedrouter.
Chapter7,ExplainingPipesandCommunicatingwithRESTfulservices,divesintotherouterandtheformsmodulesindetail.Here,wewillexplorehowwecandevelopmodel-drivenformsanddefineparameterizedandchildroutes.WewillalsoexplaintheHTTPmoduleandseehowwecandeveloppureandimpurepipes.
Chapter8,SEOandAngular2intheRealWorld,exploressomeadvancedtopicsintheAngular2applicationdevelopment,suchasrunninganapplicationinWebWorkersandserver-siderendering.Inthesecondpartofthechapter,wewillexploretoolsthatcaneaseourdailylifeasdevelopers,suchasangular-cli,andangular2-seed,explaintheconceptofhotreloading,andmore.
WhatyouneedforthisbookAllyouneedtoworkthroughmostoftheexamplesinthisbookisasimpletexteditororanIDE,Node.js,TypeScriptinstalled,Internetaccess,andabrowser.
Eachchapterintroducesthesoftwarerequirementsforrunningtheprovidedsnippets.
WhothisbookisforDoyouwanttojumpinatthedeependofAngular2?Orperhapsyou’reinterestedinassessingthechangesbeforemovingover?Ifso,thenSwitchingtoAngular2isthebookforyou.
Togetthemostoutofthebook,you’llneedtohavebasicunderstandingofAngularJS1.xandhaveagoodunderstandingofJavaScript.NoknowledgeofthechangesmadetoAngular2isrequiredtofollowalong.
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:“Youshouldseethesameresult,butwithoutthetest.jsfilestoredonthedisk.”
Ablockofcodeissetasfollows:
@Injectable()
classSocket{
constructor(privatebuffer:Buffer){}
}
letinjector=Injector.resolveAndCreate([
provide(BUFFER_SIZE,{useValue:42}),
Buffer,
Socket
]);
injector.get(Socket);
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
letinjector=Injector.resolveAndCreate([
provide(BUFFER_SIZE,{useValue:42}),
Buffer,
Socket
]);
Eachcodesnippetwhichisintherepositorywiththecodefromthisbookstartswithacommentwithitscorrespondingfilelocation,relativetotheappdirectory:
//ch5/ts/injector-basics/forward-ref.ts
@Injectable()
classSocket{
constructor(privatebuffer:Buffer){…}
}
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:“Whenthemarkupisrenderedontothescreen,allthattheuserwillseeisthelabel:Loading….”
NoteWarningsorimportantnotesappearinaboxlikethis.
TipTipsandtricksappearlikethis.
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,simplye-mail<[email protected]>,andmentionthebook’stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromGitHubathttps://github.com/mgechev/switching-to-angular2.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. EntertheURLinyourbrowser’saddressbar.2. Clickonthe“DownloadZIP”buttonlocatedinthemid-rightpartofthescreen.
Youcanalsodownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Youcanalsodownloadthecodefilesbyfollowingthesesteps:
1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORTtabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou’relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinuxChapters3and4containfurtherinformationfortheinstallationprocess.
ErrataAlthoughwehavetakeneverycaretoensuretheaccuracyofourcontent,mistakesdohappen.Ifyoufindamistakeinoneofourbooks—maybeamistakeinthetextorthecode—wewouldbegratefulifyoucouldreportthistous.Bydoingso,youcansaveotherreadersfromfrustrationandhelpusimprovesubsequentversionsofthisbook.Ifyoufindanyerrata,pleasereportthembyvisitinghttp://www.packtpub.com/submit-errata,selectingyourbook,clickingontheErrataSubmissionFormlink,andenteringthedetailsofyourerrata.Onceyourerrataareverified,yoursubmissionwillbeacceptedandtheerratawillbeuploadedtoourwebsiteoraddedtoanylistofexistingerrataundertheErratasectionofthattitle.
Toviewthepreviouslysubmittederrata,gotohttps://www.packtpub.com/books/content/supportandenterthenameofthebookinthesearchfield.TherequiredinformationwillappearundertheErratasection.
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<[email protected]>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<[email protected]>,andwewilldoourbesttoaddresstheproblem.
Chapter1.GettingStartedwithAngular2OnSeptember18,2014,thefirstpubliccommitwaspushedtotheAngular2repository.Afewweekslater,atng-europe,IgorandTobiasfromthecoreteamgaveashortoverviewofwhatAngular2wasexpectedtobe.Thevisionatthattimewasfarfromfinal;however,onethingwascertain—thenewversionoftheframeworkwouldbeentirelydifferentfromAngularJS1.x.
Thisannouncementbroughtalotofquestionsandcontroversy.Thereasonsbehindthedrasticchangeswerequiteclear—AngularJS1.xwasnolongerabletotakefulladvantageoftheevolvedWebandtocompletelysatisfytherequirementsoflarge-scaleJavaScriptapplications.AnewframeworkwouldletAngulardeveloperscapitalizeondevelopmentsinwebtechnologyinsimplerandmoredirectways.Yet,peoplewereconcerned.Oneofthebiggestnightmareswithbackwardincompatibilityfordevelopersisthemigrationoftheircurrentcodebasestothenewversionofthethird-partysoftwaretheyuse.InAngular’scase,afterthatfirstannouncement,migrationlookeddaunting,evenimpossible.Later,atng-conf2015andng-vegas,differentmigrationstrategieswereintroduced.TheAngularcommunitycametogetherandsharedadditionalideas,anticipatingthebenefitsofAngular2whilepreservingthethingslearnedfromAngularJS1.x.
Thisbookisapartofthatproject.MakingtheupgradetoAngular2isnon-trivial,butitisworthit.ThemaindriversbehindthedrasticchangesinAngular2anditslackofbackwardcompatibilityaretheevolutionoftheWeb,andthelessonslearnedfromtheusageofAngularJS1.xinthewild.SwitchingtoAngular2willhelpyoutolearnthenewframeworkbyunderstandinghowwegothereandwhyAngular’snewfeaturesmakeintuitivesenseforthemodernWebinbuildinghigh-performance,scalable,single-pageapplications.
TheevolutionoftheWeb–timeforanewframeworkInthelastcoupleofyears,theWebhasevolvedinbigsteps.DuringtheimplementationofECMAScript5,theECMAScript6standardstarteditsdevelopment(nowknownasECMAScript2015orES2015).ES2015introducedmanychangesinthelanguagesuchasaddingbuilt-inlanguagesupportformodules,blockscopevariabledefinition,andalotofsyntacticalsugar,suchasclassesanddestructuring.
Meanwhile,WebComponentswereinvented.WebComponentsallowustodefinecustomHTMLelementsandattachbehaviortothem.SinceitishardtoextendtheexistingHTMLelementswithnewones(suchasdialogs,charts,grids,andmore)mostlybecauseofthetimerequiredforconsolidationandstandardizationoftheirAPIs,abettersolutionistoallowdeveloperstoextendtheexistingelementsthewaytheywant.WebComponentsprovideuswithanumberofbenefits,includingbetterencapsulation,bettersemanticsofthemarkupweproduce,bettermodularity,andeasiercommunicationbetweendevelopersanddesigners.
WeknowthatJavaScriptisasingle-threadedlanguage.Initially,itwasdevelopedforsimpleclient-sidescripting,butovertime,itsrolehasshiftedquiteabit.NowwithHTML5,wehavedifferentAPIsthatallowaudioandvideoprocessing,communicationwithexternalservicesthroughatwo-directionalcommunicationchannel,transferringandprocessingbigchunksofrawdata,andmore.Alltheseheavycomputationsinthemainthreadmaycreateapooruserexperience.Theymayintroducefreezingoftheuserinterfacewhentime-consumingcomputationsarebeingperformed.ThisledtothedevelopmentofWebWorkers,whichallowtheexecutionofthescriptsinthebackgroundthatcommunicatewiththemainthreadthroughmessagepassing.Thisway,multi-threadedprogramminghasbeenbroughttothebrowser.
SomeoftheseAPIswereintroducedafterthedevelopmentofAngularJS1.xhadbegun;that’swhytheframeworkwasn’tbuildwithmostoftheminmind.However,exploitingtheAPIsgivesdevelopersmanybenefits,suchas:
Significantperformanceimprovements.Developmentofsoftwarewithbetterqualitycharacteristics.
Nowlet’sbrieflydiscusshoweachofthesetechnologieshasbeenmadepartofthenewAngularcoreandwhy.
TheevolutionofECMAScriptNowadays,browservendorsarereleasingnewfeaturesinshortiterations,andusersreceiveupdatesquiteoften.ThishelpsmovetheWebforwardbyallowingdeveloperstotakeadvantageofthebleeding-edgetechnologies,aimingtoimprovetheWeb.ES2015isalreadystandardized.Theimplementationofthelatestversionofthelanguagehasalreadystartedinthemajorbrowsers.Learningthenewsyntaxandtakingadvantageofitwillnotonlyincreaseourproductivityasdevelopers,butalsoprepareusforthenearfuturewhenallthebrowserswillhavefullsupportforit.Thismakesitessentialtostartusingthelatestsyntaxnow.
Someprojects’requirementsmayenforceustosupportolderbrowsers,whichdoesnotsupportanyES2015features.Inthiscase,wecandirectlywriteECMAScript5,whichhasdifferentsyntaxbutequivalentsemanticstoES2015.However,wecantakeadvantageoftheprocessoftranspilation.UsingatranspilerinourbuildprocessallowsustotakeadvantageofthenewsyntaxbywritingES2015andtranslatingittoatargetlanguagethatissupportedbythebrowsers.
AngularJShasbeenaroundsince2009.Backthen,thefrontendofmostwebsiteswaspoweredbyECMAScript3,thelastmainreleaseofECMAScriptpriortoECMAScript5.Thisautomaticallymeantthatthelanguageusedfortheframework’simplementationwasECMAScript3.TakingadvantageofthenewversionofthelanguagerequiresportingoftheentiretyofAngularJS1.xtoES2015.
Fromthebeginning,Angular2tookintoaccountthecurrentstateoftheWebbybringingthelatestsyntaxintheframework.AlthoughAngular2iswrittenwithasupersetofES2016(TypeScript,whichwe’regoingtotakealookatinamoment),itallowsdeveloperstouselanguageoftheirownpreference.WecanuseES2015or,ifweprefernottohaveanyintermediatepreprocessingofourcodeandsimplifythebuildprocess,evenECMAScript5.
WebComponentsThefirstpublicdraftofWebComponentswaspublishedonMay22,2012,aboutthreeyearsafterthereleaseofAngularJS1.x.Asmentioned,theWebComponentsstandardallowsustocreatecustomelementsandattachbehaviortothem.Itsoundsfamiliar;we’vealreadyusedsimilarconceptinthedevelopmentoftheuserinterfaceinAngularJS1.xapplications.WebComponentssoundlikeanalternativetoAngulardirectives;however,theyhavemoreintuitiveAPI,richerfunctionality,andbuilt-inbrowsersupport.Theyintroducedafewotherbenefitssuchasbetterencapsulation,whichisveryimportant,forexample,inhandlingCSS-stylecollisions.
ApossiblestrategyforaddingWebComponentssupportinAngularJS1.xistochangethedirectivesimplementationandintroduceprimitivesofthenewstandardintheDOMcompiler.AsAngulardevelopers,weknowhowpowerfulbutalsocomplexthedirectivesAPIis.ItincludesalotofpropertiessuchaspostLink,preLink,compile,restrict,scope,controller,andmanymore,andofcourse,ourfavoritetransclude.Approvedasstandard,WebComponentswillbeimplementedonamuchlowerlevelinthebrowsers,whichintroducesplentyofbenefitssuchasbetterperformanceandnativeAPI.
DuringtheimplementationofWebComponents,alotofwebspecialistsmetthesameproblemstheAngularteamdidwhendevelopingthedirectivesAPIandcameupwithsimilarideas.GooddesigndecisionsbehindWebComponentsincludethecontentelement,whichdealswiththeinfamoustransclusionprobleminAngularJS1.x.SinceboththedirectivesAPIandWebComponentssolvesimilarproblemsindifferentways,keepingthedirectivesAPIontopofWebComponentswouldhavebeenredundantandaddedunnecessarycomplexity.That’swhytheAngularcoreteamdecidedtostartfromthebeginningbybuildingontopofWebComponentsandtakingfulladvantageofthenewstandard.WebComponentsinvolvesnewfeatures,someofthemnotyetimplementedbyallbrowsers.Incaseourapplicationisruninabrowser,whichdoesnotsupportanyofthesefeaturesnatively,Angular2emulatesthem.Anexampleforthisisthecontentelementpolyfilledwiththedirective,ng-content.
WebWorkersJavaScriptisknownforitseventloop.UsuallyJavaScriptprogramsareexecutedinasinglethreadanddifferenteventsarescheduledbybeingpushedinaqueueandprocessedsequentially,intheorderoftheirarrival.However,thiscomputationalstrategyisnoteffectivewhenoneofthescheduledeventsrequiresalotofcomputationaltime.Insuchcasestheevent’shandlingisgoingtoblockthemainthreadandallothereventsarenotgoingtobehandleduntilthetimeconsumingcomputationiscompleteandpassestheexecutiontothenextoneinthequeue.Asimpleexampleofthisisamouseclickthattriggersanevent,inwhichcallbackwedosomeaudioprocessingusingtheHTML5audioAPI.Iftheprocessedaudiotrackisbigandthealgorithmrunningoveritisheavy,thiswillaffecttheuser’sexperiencebyfreezingtheUIuntiltheexecutioniscomplete.
TheWebWorkerAPIwasintroducedinordertopreventsuchpitfalls.Itallowsexecutionofheavycomputationsinsidethecontextofdifferentthread,whichleavesthemainthreadofexecutionfree,capableofhandlinguserinputandrenderingtheuserinterface.
HowcanwetakeadvantageofthisinAngular?Inordertoanswerthisquestion,let’sthinkabouthowthingsworkinAngularJS1.x.Whatifwehaveanenterpriseapplication,whichprocessesahugeamountofdatathatneedstoberenderedonthescreenusingdatabinding?Foreachbinding,anewwatcherwillbeadded.Oncethedigestloopisrun,itwillloopoverallthewatchers,executetheexpressionsassociatedwiththem,andcomparethereturnedresultswiththeresultsgainedfromthepreviousiteration.Wehaveafewslowdownshere:
Theiterationoverlargenumberofwatchers.Evaluationofexpressioningivencontext.Copyofthereturnedresult.Comparisonbetweenthecurrentresultoftheexpression’sevaluationandthepreviousone.
Allthesestepscouldbequiteslowdependingonthesizeoftheinput.Ifthedigestloopinvolvesheavycomputations,whynotmoveittoaWebWorker?WhynotrunthedigestloopinsideWebWorker,getthechangedbindings,andapplythemtotheDOM?
Therewereexperimentsbythecommunity,whichaimedforthisresult.However,theirintegrationintotheframeworkwasn’ttrivial.OneofthemainreasonsbehindthelackofsatisfyingresultswasthecouplingoftheframeworkwiththeDOM.Often,insidethewatchers’callbacks,AngulardirectlymanipulatestheDOM,whichmakesitimpossibletomovethewatchersinsideWebWorkerssincetheWebWorkersareinvokedinanisolatedcontext,withoutaccesstotheDOM.InAngularJS1.x,wemayhaveimplicitorexplicitdependenciesbetweenthedifferentwatchers,whichrequiremultipleiterationsofthedigestloopinordertogetstableresults.Combiningthelasttwopoints,itisquitehardtoachievepracticalresultsincalculatingthechangesinthreadsotherthanthemainthreadofexecution.
FixingthisinAngularJS1.xintroducesagreatdealofcomplexityintheinternalimplementation.Theframeworksimplywasnotbuiltwiththisinmind.Since
WebWorkerswereintroducedbeforetheAngular2designprocessstarted,thecoreteamtooktheminmindfromthebeginning.
LessonslearnedfromAngularJS1.xinthewildAlthoughtheprevioussectionintroducedalotofargumentsfortherequiredreimplementationoftheframeworkrespondingtothelatesttrends,it’simportanttorememberthatwe’renotstartingcompletelyfromscratch.We’retakingwhatwe’velearnedfromAngularJS1.xwithus.Intheperiodsince2009,theWebisnottheonlythingthatevolved.Wealsostartedbuildingmoreandmorecomplexapplications.Today,single-pageapplicationsarenotsomethingexotic,butmorelikeastrictrequirementforallthewebapplicationssolvingbusinessproblems,whichareaimingforhighperformanceandgooduserexperience.
AngularJS1.xhelpedustobuildhighly-efficientandlarge-scalesingle-pageapplications.However,byapplyingitinvarioususecases,we’vealsodiscoveredsomeofitspitfalls.Learningfromthecommunity’sexperience,Angular’scoreteamworkedonnewideasaimingtoanswerthenewrequirements.AswelookatthenewfeaturesofAngular2,let’sconsidertheminthelightofthecurrentimplementationofAngularJS1.xandthinkaboutthethingswithwhichwe,asAngulardevelopers,havestruggledandwhichwehavemodifiedoverthelastfewyears.
ControllersAngularJS1.xfollowstheModelViewController(MVC)micro-architecturalpattern.SomemayarguethatitlooksmorelikeModelViewViewModel(MVVM)becauseoftheviewmodelattachedaspropertiestothescopeorthecurrentcontextincaseofcontrollerassyntax.ItcouldbeapproacheddifferentlyagainifweusetheModelViewPresenterpattern(MVP).BecauseofallthedifferentvariationsofhowwecanstructurethelogicinourapplicationsthecoreteamcalledAngularJS1.xaModelViewWhatever(MVW)framework.
TheviewinanyAngularJSapplicationissupposedtobeacompositionofdirectives.Thedirectivescollaboratetogetherinordertodeliverfullyfunctionaluserinterfaces.Servicesareresponsibleforencapsulatingthebusinesslogicoftheapplications.That’stheplacewhereweshouldputthecommunicationwithRESTfulservicesthroughHTTP,real-timecommunicationwithWebSocketsandevenWebRTC.Servicesarethebuildingblockwhereweshouldimplementthedomainmodelsandbusinessrulesofourapplications.There’sonemorecomponent,whichismostlyresponsibleforhandlinguserinputanddelegatingtheexecutiontotheservices—thecontroller.
Althoughtheservicesanddirectiveshavewell-definedroles,wecanoftenseetheanti-patternoftheMassiveViewController,whichiscommoniniOSapplications.Occasionally,developersaretemptedtoaccessorevenmanipulatetheDOMdirectlyfromtheircontrollers.Initially,thishappensforachievingsomethingsimple,suchaschangingthesizeofanelement,orquickanddirtychangingelements’styles.Anothernoticeableanti-patternisduplicationofbusinesslogicacrosscontrollers.Oftendeveloperstendtocopyandpastelogic,whichshouldbeencapsulatedinsideservices.
ThebestpracticesforbuildingAngularJSapplicationsstateisthatthecontrollersshouldnotmanipulatetheDOMatall,insteadallDOMaccessandmanipulationsshouldbeisolatedindirectives.Ifwehavesomerepetitivelogicbetweencontrollers,mostlikelywewanttoencapsulateitintoaserviceandinjectthisservicewiththedependencyinjectionmechanismofAngularJSinallthecontrollersthatneedthatfunctionality.
Thisiswherewe’recomingfrominAngularJS1.x.Allthissaid,itseemsthatthefunctionalityofcontrollerscouldbemovedintothedirective’scontrollers.SincedirectivessupportthedependencyinjectionAPI,afterreceivingtheuser’sinput,wecandirectlydelegatetheexecutiontoaspecificservice,alreadyinjected.ThisisthemainreasonAngular2usesadifferentapproachbyremovingtheabilitytoputcontrollerseverywherebyusingtheng-controllerdirective.We’lltakealookathowAngularJS1.xcontrollers’responsibilitiescouldbetakenfromAngular2componentsanddirectivesinChapter4,GettingStartedwithAngular2ComponentsandDirectives.
ScopeThedata-bindinginAngularJSisachievedusingthescopeobject.Wecanattachpropertiestoitandexplicitlydeclareinthetemplatethatwewanttobindtotheseproperties(oneortwo-way).Althoughtheideaofthescopeseemsclear,thescopehastwomoreresponsibilities,includingeventdispatchingandthechangedetection-relatedbehavior.Angularbeginnershaveahardtimeunderstandingwhatscopereallyisandhowitshouldbeused.AngularJS1.2introducedsomethingcalledcontrollerassyntax.Itallowsustoaddpropertiestothecurrentcontextinsidethegivencontroller(this),insteadofexplicitlyinjectingthescopeobjectandlateraddingpropertiestoit.Thissimplifiedsyntaxcanbedemonstratedfromthefollowingsnippet:
<divng-controller="MainCtrlasmain">
<buttonng-click="main.clicked()">Click</button>
</div>
functionMainCtrl(){
this.name='Foobar';
}
MainCtrl.prototype.clicked=function(){
alert('Youclickedme!');
};
Angular2tookthisevenfurtherbyremovingthescopeobject.AlltheexpressionsareevaluatedinthecontextofgivenUIcomponent.RemovingtheentirescopeAPIintroduceshighersimplicity;wedon’tneedtoexplicitlyinjectitanymoreandweaddpropertiestotheUIcomponentstowhichwecanlaterbind.ThisAPIfeelsmuchsimplerandmorenatural.
We’regoingtotakemoredetailedlookatthecomponentsandthechangedetectionmechanismofAngular2inChapter4,GettingStartedwithAngular2ComponentsandDirectives.
DependencyInjectionMaybethefirstframeworkonthemarketthatincludedinversionofcontrol(IoC)throughdependencyinjection(DI)intheJavaScriptworldwasAngularJS1.x.DIprovidesanumberofbenefits,suchaseasiertestability,bettercodeorganizationandmodularization,andsimplicity.AlthoughtheDIin1.xdoesanamazingjob,Angular2takesthisevenfurther.SinceAngular2isontopofthelatestwebstandards,itusestheECMAScript2016decorators’syntaxforannotatingthecodeforusingDI.DecoratorsarequitesimilartothedecoratorsinPythonorannotationsinJava.Theyallowustodecoratethebehaviorofagivenobjectbyusingreflection.Sincedecoratorsarenotyetstandardizedandsupportedbymajorbrowsers,theirusagerequiresanintermediatetranspilationstep;however,ifyoudon’twanttotakeit,youcandirectlywritealittlebitmoreverbosecodewithECMAScript5syntaxandachievethesamesemantics.
ThenewDIismuchmoreflexibleandfeature-rich.ItalsofixessomeofthepitfallsofAngularJS1.xsuchasthedifferentAPIs;in1.x,someobjectsareinjectedbyposition(suchasthescope,element,attributes,andcontrollerinthedirectives’linkfunction)andothers,byname(usingparametersnamesincontrollers,directives,services,andfilters).
WewilltakeafurtherlookattheAngular2’sdependencyinjectionAPIinChapter5,DependencyInjectioninAngular2.
Server-siderenderingThebiggertherequirementsoftheWebare,themorecomplexthewebapplicationsbecome.Buildingareal-life,single-pageapplicationrequireswritingahugeamountofJavaScript,andincludingalltherequiredexternallibrariesmayincreasethesizeofthescriptsonourpagetoafewmegabytes.Theinitializationoftheapplicationmaytakeuptoseveralsecondsoreventensofsecondsonmobileuntilalltheresourcesgetfetchedfromtheserver,theJavaScriptisparsedandexecuted,thepagegetsrendered,andallthestylesareapplied.Onlow-endmobiledevicesthatuseamobileInternetconnection,thisprocessmaymaketheusersgiveuponvisitingourapplication.Althoughthereareafewpracticesthatspeedupthisprocess,incomplexapplications,there’snosilverbullet.
Intheprocessoftryingtoimprovetheuserexperience,developersdiscoveredsomethingcalledserver-siderendering.Itallowsustorendertherequestedviewofasingle-pageapplicationontheserveranddirectlyprovidetheHTMLforthepagetotheuser.Later,oncealltheresourcesareprocessed,theeventlistenersandbindingscanbeaddedbythescriptfiles.Thissoundslikeagoodwaytoboosttheperformanceofourapplication.OneofthepioneersinthiswasReactJS,whichallowedpre-renderingoftheuserinterfaceontheserversideusingNode.jsDOMimplementations.Unfortunately,thearchitectureofAngularJS1.xdoesnotallowthis.TheshowstopperisthestrongcouplingbetweentheframeworkandthebrowserAPIs,thesameissuewehadinrunningthechangedetectioninWebWorkers.
Anothertypicalusecasefortheserver-siderenderingisforbuildingSearchEngineOptimization(SEO)-friendlyapplications.TherewereacoupleofhacksusedinthepastformakingtheAngularJS1.xapplicationsindexablebythesearchengines.Onesuchpractice,forinstance,istraversaloftheapplicationwithaheadlessbrowser,whichexecutesthescriptsoneachpageandcachestherenderedoutputintoHTMLfiles,makingitaccessiblebythesearchengines.
AlthoughthisworkaroundforbuildingSEO-friendlyapplicationsworks,server-siderenderingsolvesbothofthementionedissues,improvingtheuserexperienceandallowingustobuildSEO-friendlyapplicationsmuchmoreeasilyandfarmoreelegantly.
ThedecouplingofAngular2withtheDOMallowsustorunourAngular2applicationsoutsidethecontextofthebrowser.Thecommunitytookadvantageofthisbybuildingatool,allowingustoprerendertheviewsofoursingle-pageapplicationontheserversideandforwardthemtothebrowser.Atthetimeofwritingthefollowingcontent,thetoolisstillintheearlyphasesofitsdevelopmentandisoutsidetheframework’score.We’regoingtotakeafurtherlookatitinChapter8,DevelopmentExperienceandServer-SideRendering.
ApplicationsthatscaleMVWhasbeenthedefaultchoiceforbuildingsingle-pageapplicationssinceBackbone.jsappeared.Itallowsseparationofconcernsbyisolatingthebusinesslogicfromtheview,allowingustobuildwell-designedapplications.Exploitingtheobserverpattern,MVWallowslisteningformodelchangesintheviewandupdatingitwhenchangesaredetected.However,therearesomeexplicitandimplicitdependenciesbetweentheseeventhandlers,whichmakethedataflowinourapplicationsnotobviousandhardtoreasonabout.InAngularJS1.x,weareallowedtohavedependenciesbetweenthedifferentwatchers,whichrequiresthedigestlooptoiterateoverallofthemacoupleoftimesuntiltheexpressions’resultsgetstable.Angular2makesthedata-flowone-directional,whichhasanumberofbenefits,including:
Moreexplicitdata-flow.Nodependenciesbetweenbindings,sonotimetolive(TTL)ofthedigest.Betterperformance:
Thedigestloopisrunonlyonce.Wecancreateapps,whicharefriendlytoimmutable/observablemodels,thatallowsustomakefurtheroptimizations.
Thechangeinthedata-flowintroducesonemorefundamentalchangeinAngularJS1.xarchitecture.
WemaytakeanotherperspectiveonthisproblemwhenweneedtomaintainalargecodebasewritteninJavaScript.AlthoughJavaScript’sducktypingmakesthelanguagequiteflexible,italsomakesitsanalysisandsupportbyIDEsandtexteditorsharder.Refactoringoflargeprojectsgetsveryhardanderror-pronebecauseinmostcases,thestaticanalysisandtypeinferenceareimpossible.Thelackofcompilermakestyposalltooeasy,whicharehardtonoticeuntilwerunourtestsuiteorruntheapplication.
TheAngularcoreteamdecidedtouseTypeScriptbecauseofthebettertoolingpossiblewithitandthecompile-timetypechecking,whichhelpusbemoreproductiveandlesserror-prone.Astheprecedingfigureshows,TypeScriptisasupersetofECMAScript;itintroducesexplicittypeannotationsandacompiler.TheTypeScriptlanguageiscompiledtoplainJavaScript,supportedbytoday’sbrowsers.Sinceversion1.6,TypeScriptimplementstheECMAScript2016decorators,whichmakesittheperfectchoiceforAngular2.
TheusageofTypeScriptallowsmuchbetterIDEandtexteditorssupportwithstaticcodeanalysisandtypechecking.Allthisincreasesourproductivitydramaticallybyreducingthemistakeswemakeandsimplifyingtherefactoringprocess.AnotherimportantbenefitofTypeScriptistheperformanceimprovementweimplicitlygetbythestatictyping,whichallowsrun-timeoptimizationsbytheJavaScriptvirtualmachine.
We’llbetalkingaboutTypeScriptindetailinChapter3,TypeScriptCrashCourse.
TemplatesTemplatesareoneofthekeyfeaturesinAngularJS1.x.TheyaresimpleHTMLanddonotrequireanyintermediateprocessingandcompilation,unlikemosttemplateenginessuchasmustache.TemplatesinAngularJScombinesimplicitywithpowerbyallowingustoextendHTMLbycreatinganinternalDomainSpecificLanguage(DSL)insideit,withcustomelementsandattributes.
However,thisisoneofthemainpurposesbehindwebcomponentsaswell.WealreadymentionedhowandwhyAngular2takesadvantageofthisnewtechnology.AlthoughAngularJS1.xtemplatesaregreat,theycanstillgetbetter!Angular2templatestookthebestpartsoftheonesinthepreviousreleaseoftheframeworkandenhancedthembyfixingsomeoftheirconfusingparts.
Forexample,let’ssaywebuiltadirectiveandwewanttoallowtheusertopassapropertytoitbyusinganattribute.InAngularJS1.x,wecanapproachthisinthreedifferentways:
<username="literal"></user>
<username="expression"></user>
<username="{{interpolate}}"></user>
Ifwehaveadirectiveuserandwewanttopassthenameproperty,wecanapproachinthreedifferentways.Wecaneitherpassaliteral(inthiscase,thestring"literal"),astring,whichwillbeevaluatedasanexpression(inourcase"expression"),oranexpressioninside{{}}.Whichsyntaxshouldbeusedcompletelydependsonthedirective’simplementation,whichmakesitsAPI-tangledandhardtoremember.
Itisafrustratingtasktodealwithlargeamountofcomponentswithdifferentdesigndecisionsonadailybasis.Byintroducingacommonconvention,wecandealwithsuchproblems.However,inordertohavegoodresultsandconsistentAPIs,theentirecommunityneedstoagreewithit.
Angular2dealswiththisproblemaswellbyprovidingspecialsyntaxforattributes,whosevaluesneedtobeevaluatedinthecontextofthecurrentcomponent,andadifferentsyntaxforpassingliterals.
Anotherthingwe’reusedto,basedonourAngularJS1.xexperience,isthemicrosyntaxusedintemplatedirectivessuchasng-if,ng-for.Forinstance,ifwewanttoiterateoveralistofusersanddisplaytheirnamesinAngularJS1.x,wecanuse:
<divng-for="userinusers">{{user.name}}</div>
Althoughthissyntaxlooksintuitivetous,itallowslimitedtoolingsupport.However,Angular2approachedthisdifferentlybybringingalittlebitmoreexplicitsyntaxwithrichersemantics:
<templatengForvar-user[ngForOf]="users">
{{user.name}}
</template>
Theprecedingsnippetexplicitlydefinestheproperty,whichhastobecreatedinthe
contextofthecurrentiteration(user),theoneweiterateover(users).
However,thissyntaxistooverbosefortyping.Developerscanusethefollowingsyntax,whichlatergetstranslatedtothemoreverboseone:
<li*ngFor="#userofusers">
{{user.name}}
</li>
TheimprovementsinthenewtemplateswillalsoallowbettertoolingforadvancedsupportbytexteditorsandIDEs.We’regoingtodiscussAngular2’stemplatesinChapter4,GettingStartedwithAngular2ComponentsandDirectives.
ChangedetectionIntheWebWorkerssection,wealreadymentionedtheopportunitytorunthedigestloopinthecontextofadifferentthread,instantiatedasWebWorker.However,theimplementationofthedigestloopinAngularJS1.xisnotquitememory-efficientandpreventstheJavaScriptvirtualmachinefromdoingfurthercodeoptimizations,whichallowssignificantperformanceimprovements.Onesuchoptimizationistheinlinecaching(http://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html).TheAngularteamdidalotofresearchdiscoveringdifferentwaystheperformanceandtheefficiencyofthedigestloopcouldbeimproved.Thisledtothedevelopmentofabrandnewchangedetectionmechanism.
Inordertoallowfurtherflexibility,theAngularteamabstractedthechangedetectionanddecoupleditsimplementationfromtheframework’score.Thisallowedthedevelopmentofdifferentchangedetectionstrategies,empoweringdifferentfeaturesindifferentenvironments.
Asaresult,Angular2hastwobuilt-inchangedetectionmechanisms:
Dynamicchangedetection:ThisissimilartothechangedetectionmechanismusedbyAngularJS1.x.Itisusedinsystemswithdisallowedeval(),suchasCSPandChromeextensions.JITchangedetection:Thisgeneratesthecodethatperformsthechangedetectionrun-time,allowingtheJavaScriptvirtualmachinetoperformfurthercodeoptimizations.
We’regoingtotakealookatthenewchangedetectionmechanismsandhowwecanconfiguretheminChapter4,GettingStartedwithAngular2ComponentsandDirectives.
SummaryInthischapter,weconsideredthemainreasonsbehindthedecisionstakenbytheAngularcoreteamandthelackofbackwardcompatibilitybetweenthelasttwomajorversionsoftheframework.Wesawthatthesedecisionswerefueledbytwothings—theevolutionoftheWebandtheevolutionofthefrontenddevelopment,withthelessonslearnedfromthedevelopmentofAngularJS1.xapplications.
Inthefirstsection,welearnedwhyweneedtousethelatestversionoftheJavaScriptlanguage,whywewanttotakeadvantageofWebComponentsandWebWorkers,andwhyit’snotworthittointegrateallthesepowerfultoolsinversion1.x.
Weobservedthecurrentdirectionoffrontenddevelopmentandthelessonslearnedinthelastfewyears.WedescribedwhythecontrollerandscopewereremovedfromAngular2,andwhyAngularJS1.x’sarchitecturewaschangedinordertoallowserver-siderenderingforSEO-friendly,high-performance,single-pageapplications.Anotherfundamentaltopicwetookalookatwasbuildinglarge-scaleapplications,andhowthatmotivatedsingle-waydata-flowintheframeworkandthechoiceofthestatically-typedlanguageTypeScript.
Inthenextchapter,we’regoingtolookatthemainbuildingblocksofanAngular2application—howtheycanbeusedandhowtheyrelatetoeachother.Angular2reusessomeofthenamingofthecomponentsintroducedbyAngularJS1.x,butgenerallychangesthebuildingblocksofoursingle-pageapplicationscompletely.We’regoingtopeekatthenewcomponents,andcomparethemwiththeonesinthepreviousversionoftheframework.We’llmakeaquickintroductiontodirectives,components,routers,pipes,andservices,anddescribehowtheycouldbecombinedforbuildingclassy,single-pageapplications.
TipDownloadingtheexamplecode
Youcandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.HoverthemousepointerontheSUPPORTtabatthetop.ClickonCodeDownloads&Errata.EnterthenameofthebookintheSearchbox.Selectthebookforwhichyou’relookingtodownloadthecodefiles.Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.ClickonCodeDownload.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
Chapter2.TheBuildingBlocksofanAngular2ApplicationInthepreviouschapter,welookedatthedriversforthedesigndecisionsbehindAngular2.Wedescribedthemainreasonsthatledtothedevelopmentofabrandnewframework;Angular2takesadvantageofwebstandardswhilekeepingthepastlessonsinmind.Althoughwearefamiliarwiththemaindrivers,westillhaven’tdescribedthecoreAngular2concepts.ThelastmajorreleaseoftheframeworktookadifferentpathfromAngularJS1.xandintroducedalotofchangesinthefundamentalbuildingblocksusedforthedevelopmentofsingle-pageapplications.
Inthischapter,we’lllookattheframework’scoreandmakeabriefintroductiontothemaincomponentsofAngular2.Anotherimportantpurposeofthischapteristotakeanoverviewofhowtheseconceptscanbeputtogethertohelpusbuildprofessionaluserinterfacesforourwebapplications.Thefollowingsectionswillgiveusanoverviewofeverythingthatwearegoingtotakealookatinmoredetaillaterinthebook.
Inthischapter,we’regoingtolookat:
Aconceptualoverviewoftheframework,showinghowdifferentconceptsrelatetoeachother.Howwecanbuildauserinterfaceasacompositionofcomponents.WhatpaththedirectivestookinAngular2,andhowtheirinterfacechangedcomparedtothepreviousmajorversionoftheframework.Thereasonsfortheenforcedseparationofconcerns,whichledtothedecompositionofthedirectivesintotwodifferentcomponents.Inordertogetbettersenseofthesetwoconcepts,we’regoingtodemonstratebasicsyntaxfortheirdefinition.Anoverviewoftheimprovedchangedetection,andhowitinvolvesthecontextthatdirectivesprovide.Whatzonesare,andwhytheycanmakeourdailydevelopmentprocesseasier.Whatpipesare,andhowaretheyrelatedtotheAngularJS1.xfilters.Thebrand-newdependencyinjection(DI)mechanisminAngular2andhowitisrelatedtotheservicecomponent.
AconceptualoverviewofAngular2BeforewediveintodifferentpartsofAngular2,let’sgetaconceptualoverviewofhoweverythingfitstogether.Let’shavealookatthefollowingdiagram:
Fig.1
Fig.1toFig.4showsthemainAngular2conceptsandtheconnectionsbetweenthem.Themainpurposeofthesediagramsistoillustratethecoreblocksforbuildingsingle-pageapplicationswithAngular2,andtheirrelations.
TheComponentisthemainbuildingblockwe’regoingtousetocreatetheuserinterfaceofourapplicationswithAngular2.TheComponentisadirectsuccessoroftheDirective,whichistheprimitiveforattachingbehaviortotheDOM.ComponentsextendDirectivesbyprovidingfurtherfeatures,suchasaviewwithanattachedtemplate,whichcanbeusedforrenderingcompositionofdirectives.Insidethetemplateoftheviewcanresidedifferentexpressions.
Fig.2
TheprecedingdiagramillustratesconceptuallytheChangeDetectionmechanismofAngular2.Itrunsthedigestloop,whichevaluatestheregisteredexpressionsinthecontextofspecificUIcomponents.SincetheconceptofscopehasbeenremovedfromAngular2,theexecutioncontextoftheexpressionsarethecontrollersofthecomponentsassociatedwiththem.
TheChangeDetectionmechanismcanbeenhancedusingDiffers;that’swhythere’sadirectrelationbetweenthesetwoelementsonthediagram.
PipesareanothercomponentofAngular2.WecanthinkofthePipesasthefiltersfromAngularJS1.x.Pipescanbeusedtogetherwithcomponents.Wecanincludethemintheexpressions,whicharedefinedinthecontextofanycomponent:
Fig.3
Nowlet’stakealookattheprecedingdiagram.DirectivesandComponentsdelegatethebusinesslogictoServices.Thisenforcesbetterseparationofconcerns,maintainability,andreusabilityofcode.DirectivesreceivereferencestoinstancesofspecificservicesdeclaredasdependenciesusingtheDImechanismoftheframework,anddelegatetheexecutionofthebusinessrelatedlogictothem.BothDirectivesandComponentsmayusetheDImechanism,notonlytoinjectservices,butalsotoinjectDOMelementsand/orotherComponentsorDirectives.
Fig.4
Lastly,thecomponent-basedrouterisusedfordefiningtheroutesinourapplication.SinceDirectivesdonotownatemplate,onlytheComponentscanberenderedbytherouter,representingthedifferentviewsinourapplication.Therouteralsousesthepredefineddirectives,whichallowustodefinehyperlinksbetweenthedifferentviewsandthecontainerwheretheyshouldberendered.
Nowwe’regoingtolookmorecloselyattheseconcepts,seehowtheyworktogethertomakeAngular2applications,andhowthey’vechangedfromtheirAngularJS1.xpredecessors.
ChangingdirectivesAngularJS1.xintroducedtheconceptofdirectivesinthedevelopmentofsingle-pageapplications.ThepurposeofdirectivesistoencapsulatetheDOM-relatedlogic,andallowustobuilduserinterfacesascompositionsofsuchcomponentsbyextendingthesyntaxandthesemanticsofHTML.Initially,likemostinnovativeconcepts,directiveswereviewedcontroversiallybecausetheypredisposeustowriteinvalidHTMLwhenusingcustomelementsorattributeswithoutthedata-prefix.However,overtime,thisconcepthasgraduallybeenaccepted,andhasprovedthatitisheretostay.
AnotherdrawbackoftheimplementationofdirectivesinAngularJS1.xarethedifferentwayswecanusethem.Thisrequiresunderstandingoftheattributevalues,whichcanbeliterals,expressions,callbacks,ormicrosyntax.Thismakestoolingessentiallyimpossible.
Angular2keepstheconceptofdirectives,buttakesthebestpartsfromAngularJS1.xandaddssomenewideasandsyntax.ThemainpurposeofAngular2’sdirectivesistoattachbehaviortotheDOMbyextendingitwithcustomlogicdefinedinanES2015class.Wecanthinkoftheseclassesascontrollersassociatedtothedirectives,andthinkoftheirconstructorsassimilartothelinkingfunctionofthedirectivesfromAngularJS1.x.However,thenewdirectiveshavelimitedconfigurability.Theydonotallowforthedefinitionofatemplate,whichmakesmostofthealreadyknownpropertiesfordefiningdirectivesunnecessary.ThesimplicityofthedirectivesAPIdoesnotlimittheirbehavior,butonlyenforcesstrongerseparationofconcerns.TocomplementthismoresimpledirectiveAPI,Angular2hasintroducedaricherinterfaceforthedefinitionofUIelements,calledcomponents.Componentsextendthefunctionalityofdirectivesbyallowingthemtoownatemplate,throughtheComponentmetadata.We’regoingtotakeafurtherlookatcomponentslater.
ThesyntaxusedforAngular2directivesinvolvesES2016decorators.However,wecanalsouseTypeScript,ES2015,orevenECMAScript5(ES5)inordertoachievethesameresultwithalittlebitmoretyping.Thefollowingcodedefinesasimpledirective,writteninTypeScript:
@Directive({
selector:'[tooltip]'
})
exportclassTooltip{
privateoverlay:Overlay;
@Input()
privatetooltip:string;
constructor(privateel:ElementRef,manager:OverlayManager){
this.overlay=manager.get();
}
@HostListener('mouseenter')
onMouseEnter(){
this.overlay.open(this.el.nativeElement,this.tooltip);
}
@HostListener('mouseleave')
onMouseLeave(){
this.overlay.close();
}
}
Thedirectivecanbeusedwiththefollowingmarkupinourtemplate:
<divtooltip="42">Tellmetheanswer!</div>
Oncetheuserpointsoverthethelabel,Tellmetheanswer!,Angularwillinvokethemethod,definedunderthe@HostListenerdecoratorinthedirective’sdefinition.Intheend,theopenmethodoftheoverlaymanagerwillbeexecuted.Sincewecanhavemultipledirectivesonasingleelement,thebestpracticesstatethatweshoulduseanattributeasaselector.
AnalternativeECMAScript5syntaxforthedefinitionofthisdirectiveis:
varTooltip=ng.core.Directive({
selector:'[tooltip]',
inputs:['tooltip'],
host:{
'(mouseenter)':'onMouseEnter()',
'(mouseleave)':'onMouseLeave()'
}
})
.Class({
constructor:[ng.core.ElementRef,Overlay,function(tooltip,el,
manager){
this.el=el;
this.overlay=manager.get();
}],
onMouseEnter(){
this.overlay.open(this.el.nativeElement,this.tooltip);
},
onMouseLeave(){
this.overlay.close();
}
});
TheprecedingES5syntaxdemonstratestheinternalJavaScriptDomainSpecificLanguage(DSL)thatAngular2providesinordertoallowustowriteourcodewithoutthesyntax,whichisnotyetsupportedbymodernbrowsers.
WecansummarizethatAngular2haskepttheconceptofdirectivesbymaintainingtheideaofattachingbehaviortotheDOM.Thecoredifferencesbetween1.xand2arethenewsyntax,andthefurtherseparationofconcernsintroducedbybringingthecomponents.InChapter4,GettingStartedwithAngular2ComponentsandDirectives,wewilltakeafurtherlookatdirectives’API.We’llalsocomparethedirectives’definitionsyntaxusingES2016andES5.Nowlet’shavealookatthebigchangetoAngular2components.
GettingtoknowAngular2componentsModelViewController(MVC)isamicro-architecturalpatterninitiallyintroducedfortheimplementationofuserinterfaces.AsAngularJSdevelopers,weusedifferentvariationsofthispatternonadailybasis,mostoftenModelViewViewModel(MVVM).InMVC,wehavethemodel,whichencapsulatesthebusinesslogicofourapplication,andtheview,whichisresponsibleforrenderingtheuserinterface,acceptinguserinput,aswellasdelegatingtheuserinteractionlogictothecontroller.Theviewisrepresentedasthecompositionofcomponents,whichisformallyknownasthecompositedesignpattern.
Let’stakealookatthefollowingstructuraldiagram,whichshowsthecompositedesignpattern:
Fig.5
Herewehavethreeclasses:
AnabstractclasscalledComponent.TwoconcreteclassescalledLeafandComposite.TheLeafclassisasimpleterminalcomponentinthecomponenttreethatwe’regoingtobuildsoon.
TheComponentclassdefinesanabstractoperationcalledoperation.BothLeafandCompositeinheritfromtheComponentclass.However,theCompositeclassalsoownsreferencestoit.WecantakethisevenfurtherandallowCompositetoownalistofreferencestoComponentinstances,asshowninthediagram.ThecomponentslistinsideCompositecanholdreferencestodifferentCompositeorLeafinstances,orinstancesofotherclasses,whichextendtheComponentclassoranyofitssuccessors.Intheimplementationofthemethod,operation,insideComposite,theinvokedoperationofthedifferentinstancesinsidetheloopcanbehavedifferently.Thisisbecauseofthelatebindingmechanismusedfortheimplementationofthepolymorphisminobject-orientedprogramminglanguages.
ComponentsinactionEnoughoftheory!Let’sbuildacomponenttreebasedontheclasshierarchyillustratedinthediagram.Thisway,we’regoingtodemonstratehowwecantakeadvantageofthecompositepatternforbuilding,userinterfacebyusingsimplifiedsyntax.We’regoingtotakealookatasimilarexampleinthecontextofAngular2inChapter4,GettingStartedwithAngular2ComponentsandDirectives:
Compositec1=newComposite();
Compositec2=newComposite();
Compositec3=newComposite();
c1.components.push(c2);
c1.components.push(c3);
Leafl1=newLeaf();
Leafl2=newLeaf();
Leafl3=newLeaf();
c2.components.push(l1);
c2.components.push(l2);
c3.components.push(l3);
Theprecedingpseudo-codecreatesthreeinstancesoftheCompositeclassandthreeinstancesoftheLeafclass.Theinstance,c1,holdsreferencestoc2andc3insidethecomponentslist.Theinstance,c2,holdsreferencestol1andl2,andc3holdsreferencetol3:
Fig.6
Theprecedingdiagramisagraphicalrepresentationofthecomponenttreewebuiltinthesnippet.ThisisaquitesimplifiedversionofwhattheviewinthemodernJavaScriptframeworkslookssimilarto.However,itillustratestheverybasicsofhowwecancomposedirectivesandcomponents.Forinstance,inthecontextofAngular2wecanthinkofdirectivesasinstancesoftheprecedingLeafclass(sincetheydon’townviewandthuscannotcomposeotherdirectivesandcomponents),andcomponentsasinstancesoftheCompositeclass.
IfwethinkmoreabstractlyfortheuserinterfaceinAngularJS1.x,wecannoticethatweusequiteasimilarapproach.Thetemplatesofourviewsarecomposingdifferentdirectivestogetherinordertodeliverfullyafunctionaluserinterfacetotheenduserofourapplication.
ComponentsinAngular2Angular2tookthisapproachbyintroducingnewbuildingblockscalledcomponents.Componentsextendthedirectiveconceptwedescribedintheprevioussection,andprovidebroaderfunctionality.Hereisthedefinitionofabasichello-worldcomponent:
@Component({
selector:'hello-world',
template:'<h1>Hello,{{this.target}}!</h1>'
})
classHelloWorld{
target:string;
constructor(){
this.target='world';
}
}
Wecanuseitbyinsertingthefollowingmarkupinourview:
<hello-world></hello-world>
Accordingtothebestpractices,weshoulduseanelementasaselectorforourcomponentssincewemayhaveonlyasinglecomponentperDOMelement.
ThealternativeES5syntaxusingtheDSLAngularprovidesis:
varHelloWorld=ng.core.
Component({
selector:'hello-world',
template:'<h1>Hello,{{target}}!</h1>'
})
.Class({
constructor:function(){
this.target='world';
}
});
Wewilltakealookattheprecedingsyntaxinmoredetaillaterinthebook.However,let’sbrieflydescribethefunctionality,whichthiscomponentprovides.OncetheAngular2applicationhasbeenbootstrapped,itwilllookatalltheelementsofourDOMtreeandprocessthem.Onceitfindstheelementcalledhello-world,itwillinvokethelogicassociatedwithitsdefinition,whichmeansthatthetemplateofthecomponentwillberenderedandtheexpressionbetweenthecurlybracketswillbeevaluated.Thiswillresulttothemarkup,<h1>Hello,world!</h1>.
Sotosummarize,theAngularcoreteamseparatedoutthedirectivesfromAngularJS1.xintotwodifferentparts—ComponentsandDirectives.DirectivesprovideaneasywaytoattachbehaviortoDOMelementswithoutdefiningaview.ComponentsinAngular2provideapowerful,andyetsimple-to-learnAPI,whichmakesiteasiertodefinetheuserinterfaceofourapplications.Angular2ComponentsallowustodothesameamazingthingsasAngularJS1.xdirectives,butwithlesstypingandfewerthingstolearn.ComponentsextendtheAngular2directiveconceptbyaddingaviewtoit.Wecanthink
oftherelationbetweenAngular2componentsanddirectivesthesamewayastherelationbetweenCompositeandLeaffromthediagramwesawinFig.5.
IfwestartillustratingtheconceptualmodelofthebuildingblocksAngular2provides,wecanpresenttherelationbetweenDirectiveandComponentasinheritance.Chapter4,GettingStartedwithAngular2ComponentsandDirectivesdescribesthesetwoconceptsinfurtherdetails.
PipesInbusinessapplications,weoftenneedtohavedifferentvisualrepresentationsofthesamepieceofdata.Forexample,ifwehavethenumber100,000andwewanttoformatitascurrency,mostlikelywewon’twanttodisplayitasplaindata;morelikely,we’llwantsomethinglike$100,000.
TheresponsibilityforformattingdatainAngularJS1.xwasassignedtofilters.Anotherexampleforadataformattingrequirementiswhenweusecollectionsofitems.Forinstance,ifwehavealistofitems,wemaywanttofilteritbasedonapredicate(abooleanfunction);inalistofnumbers,wemaywanttodisplayonlyprimenumbers.AngularJS1.xhasafiltercalledfilter,whichallowsustodothis.However,theduplicationofthenamesoftenleadstoconfusion.That’sanotherreasonthecoreteamrenamedthefiltercomponenttopipe.
Themotivationbehindthenewnameisthesyntaxusedforpipesandfilters:
{{expression|decimal|currency}}
Intheprecedingexample,weapplythepipes,decimalandcurrency,tothevaluereturnedbyexpression.TheentireexpressionbetweenthecurlybraceslookslikeUnixpipesyntax.
DefiningpipesThesyntaxfordefiningpipesissimilartotheoneusedforthedefinitionofdirectivesandcomponents.Inordertocreateanewpipe,wecanusetheES2015decorator,@Pipe.Itallowsustoaddmetadatatoaclass,declaringitasapipe.Allweneedtodoistoprovideanameforthepipeanddefinethedataformattinglogic.There’salsoanalternativeES5syntax,whichcanbeusedifwewanttoskiptheprocessoftranspilation.
Duringruntime,oncetheAngular2expressioninterpreterfindsoutthatagivenexpressionincludesacallofapipe,itwillretrieveitoutofthepipescollectionallocatedwithinthecomponentandinvokeitwiththeappropriatearguments.
Thefollowingexampleillustrateshowwecandefineasimplepipecalledlowercase1,whichtransformsthegivenstring,passedasargumenttoitslowercaserepresentation:
@Pipe({name:'lowercase1'})
classLowerCasePipe1implementsPipeTransform{
transform(value:string):string{
if(!value)returnvalue;
if(typeofvalue!=='string'){
thrownewError('Invalidpipevalue',value);
}
returnvalue.toLowerCase();
}
}
Inordertobeconsistent,let’sshowtheECMAScript5syntaxfordefiningpipes:
varLowercasePipe1=ng.core.
Pipe({
name:'lowercase'
})
.Class({
constructor:function(){},
transform:function(value){
if(!value)returnvalue;
if(typeofvalue==='string'){
thrownewError('Invalidpipevalue',value);
}
returnvalue.toLowerCase();
}
});
IntheTypeScriptsyntax,weimplementthePipeTransforminterfaceanddefinethetransformmethoddeclaredinsideit.However,inECMAScript5,wedonothavesupportforinterfaces,butwestillneedtoimplementthetransformmethodinordertodefineavalidAngular2pipe.WearegoingtoexplaintheTypeScriptinterfacesinthenextchapter.
Nowlet’sdemonstratehowwecanusethelowercase1pipeinsideacomponent:
@Component({
selector:'app',
pipes:[LowercasePipe1],
template:'<h1>{{"SAMPLE"|lowercase1}}</h1>'
})
classApp{}
And,thealternativeECMAScript5syntaxforthisis:
varApp=ng.core.Component({
selector:'app',
pipes:[LowercasePipe1],
template:'<h1>{{"SAMPLE"|lowercase1}}</h1>'
})
.Class({
constructor:function(){}
});
WecanusetheAppcomponentwiththefollowingmarkup:
<app></app>
Theresultwearegoingtoseeonthescreenisthetextsamplewithinanh1element.
Bykeepingthedataformattinglogicasaseparatecomponent,Angular2keepsthestrongseparationofconcernsthatcanbeseenthroughout.WewilltakealookathowwecandefinestatefulandstatelesspipesforourapplicationinChapter7,Buildingareal-lifeapplicationwhileexploringpipesandhttp.
ChangedetectionAswesawearlier,theviewinMVCupdatesitself,basedonchangeeventsitreceivesfromthemodel.AnumberofModelViewWhatever(MVW)frameworkstookthisapproach,andembeddedtheobserverpatterninthecoreoftheirchangedetectionmechanism.
ClassicalchangedetectionLet’stakealookatasimpleexample,whichdoesn’tuseanyframework.SupposewehaveamodelcalledUser,whichhasapropertycalledname:
classUserextendsEventEmitter{
privatename:string;
setName(name:string){
this.name=name;
this.emit('change');
}
getName():string{
returnthis.name;}
}
TheprecedingsnippetusesTypeScript.Donotworryifthesyntaxdoesnotlookfamiliartoyou,we’regoingtomakeanintroductiontothelanguageinthenextchapter.
Theuserclassextendstheclass,EventEmitter.Thisprovidesprimitivesforemittingandsubscribingtoevents.
Nowlet’sdefineaview,whichdisplaysthenameofaninstanceoftheUserclass,passedasanargumenttoitsconstructor:
classView{
constructor(user:User,el:Element/*aDOMelement*/){
el.innerHTML=user.getName();
}
}
Wecaninitializetheviewelementby:
letuser=newUser();
user.setName('foo');
letview=newView(user,document.getElementById('label'));
Astheendresult,theuserwillseealabelwiththecontent,foo.However,changesinuserwillnotbereflectedbytheview.Inordertoupdatetheviewwhenthenameoftheuserchanges,weneedtosubscribetothechangeeventandthenupdatethecontentoftheDOMelement.WeneedtoupdatetheViewdefinitioninthefollowingway:
classView{
constructor(user:User,el:any/*aDOMelement*/){
el.innerHTML=user.getName();
user.on('change',()=>{
el.innerHTML=user.getName();
});
}
}
ThisishowmostframeworksusedtoimplementtheirchangedetectionbeforetheeraofAngularJS1.x.
AngularJS1.xchangedetectionMostbeginnersarefascinatedbythedata-bindingmechanisminAngularJS1.x.ThebasicHelloWorldexamplelookssimilartothis:
functionMainCtrl($scope){
$scope.label='Helloworld!';
}
<bodyng-appng-controller="MainCtrl">
{{label}}
</body>
Ifyourunthis,Helloworld!magicallyappearsontothescreen.However,thatisnoteventhemostimpressivething!Ifweaddatextinputandwebindittothelabelpropertyofthescope,eachchangewillreflectthecontentdisplayedbytheinterpolationdirective:
<bodyng-controller="MainCtrl">
<inputng-model="label">
{{label}}
</body>
Howawesomeisthat!ThisisoneofthemainsellingpointsofAngularJS1.x—theextremeeaseofachievingdata-binding.Weaddtwo(fourifwecountng-controllerandng-app)attributesinourmarkup,addpropertytoamysticalobjectcalled$scope,whichismagicallypassedtoacustomfunctionwedefine,andeverythingsimplyworks!
However,themoreexperiencedAngulardeveloperhasabetterunderstandingofwhatisactuallygoingonbehindthescene.Intheprecedingexample,insidethedirectives,ng-modelandng-bind(inourcase,theinterpolationdirective,{{}}),Angularaddswatcherswithdifferentbehaviorassociatedtothesameexpression—label.ThesewatchersarequitesimilartotheobserversintheclassicalMVCpattern.Onsomespecificevents(inourcase,changeofthecontentofthetextinput),AngularJSwillloopoverallsuchwatchers,evaluatetheexpressionsassociatedtotheminthecontextofagivenscope,andstoretheirresults.Thisloopisknownasthedigestloop.
Intheprecedingexamples,theevaluationoftheexpression,label,inthecontextofthescopewillreturnthetext,Helloworld!.Oneachiteration,AngularJSwillcomparethecurrentresultoftheevaluationwiththepreviousresult,andwillinvoketheassociatedcallbackincasethevaluesdiffer.Forinstance,thecallbackaddedbytheinterpolationdirectivewillsetthecontentoftheelementtobethenewresultoftheexpression’sevaluation.Thisisanexampleofthedependencybetweenthecallbacksofthewatchersoftwodirectives.Thecallbackofthewatcheraddedbyng-modelmodifiestheresultoftheexpressionassociatedtothewatcheraddedbytheinterpolationdirective.
However,thisapproachhasitsowndrawbacks.Wesaidthatthedigestloopwillbeinvokedonsomespecificevents,butwhatiftheseeventshappenoutsidetheframework,forexample?WhatifweusesetTimeoutandinsidethecallback,passedasthefirstargument,wechangepropertiesattachedtothescopethatwe’rewatching?AngularJSwillbeunawareofthechangeandwon’tinvokethedigestloop,soweneedtodothat
explicitlyusing$scope.$apply.Butwhatiftheframeworkknewaboutalltheasynchronouseventshappeninginthebrowser,suchasuserevents,XMLHttpRequestevents,WebSocketsrelatedevents,andothers?Insuchacase,AngularJSwouldbeabletointercepttheevent’shandlingandcouldinvokethedigestloopwithoutforcingustodoso!
Inthezone.jsThat’sexactlythecaseinAngular2.Thisfunctionalityisimplementedwithzonesusingzone.js.
Atng-confin2014,BrianFordgaveatalkaboutzones.Brianpresentedzonesasmeta-monkeypatchingofbrowserAPIs.RecentlyMiškoHeveryproposedtoTC39morematurezonesAPIforstandardization.Zone.jsisalibrarydevelopedbytheAngularteam,whichimplementszonesinJavaScript.Theyrepresentanexecutioncontext,whichallowustointerceptasynchronousbrowsercalls.Basically,byusingzones,weareabletoinvokeapieceoflogicjustafterthegivenXMLHttpRequestcompletesorwhenwereceiveanewWebSocketevent.Angular2tookadvantageofzone.jsbyinterceptingasynchronousbrowsereventsandinvokingthedigestloopjustattherighttime.ThistotallyeliminatestheneedofexplicitcallsofthedigestloopbythedeveloperusingAngular.
SimplifieddataflowThecross-watcherdependenciesmaycreateatangleddataflowinourapplication,whichishardtofollow.Thismayleadtounpredictablebehaviorandbugs,whicharehardtofind.AlthoughAngular2keptthedirtycheckingasawayforachievingchangedetection,itenforcedunidirectionaldataflow.Thishappenedbydisallowingdependenciesbetweenthedifferentwatchers,whichallowsthedigestlooptoberunonlyonce.Thisstrategyincreasestheperformanceofourapplicationsdramatically,andreducesthecomplexityofthedataflow.Angular2alsomadeimprovementstomemoryefficiencyandtheperformanceofthedigestloop.FurtherdetailsonAngular2’schangedetectionandthedifferentstrategiesusedforitsimplementationcanbefoundinChapter4,GettingStartedwithAngular2ComponentsandDirectives.
EnhancingAngularJS1.x’schangedetectionNowlet’stakeastepbackandagainthinkaboutthechangedetectionmechanismoftheframework.
Wesaidthatinsidethedigestloop,Angularevaluatesregisteredexpressionsandcomparestheevaluatedvalueswiththevaluesassociatedwiththesameexpressionsinthepreviousiterationoftheloop.
Themostoptimalalgorithmusedforthecomparisonmaydifferdependingonthetypeofthevaluereturnedfromtheexpression’sevaluation.Forinstance,ifwegetamutablelistofitems,weneedtoloopovertheentirecollectionandcomparetheitemsinthecollectionsonebyoneinordertoverifythatthereisachangeornot.However,ifwehaveanimmutablelist,wecanperformacheckwithaconstantcomplexity,onlybycomparingreferences.Thisisthecasebecausetheinstancesofimmutabledatastructurescannotchange.Insteadofapplyinganoperation,whichintendstomodifysuchinstances,we’llgetanewreferencewiththemodificationapplied.
InAngularJS1.x,wecanaddwatchersusingafewmethods.Twoofthemare$watch(exp,fn,deep)or$watchCollection(exp,fn).Thesemethodsgiveussomelevelofcontroloverthewaythechangedetectionwillperformtheequalitycheck.Forexample,addingawatcherbyusing$watchandpassingafalsevalueasathirdargumentwillmakeAngularJSperformareferencecheck(thatiscomparethecurrentvaluewiththepreviousoneusing===).However,ifwepassatruthy(anytruevalue),thecheckwillbedeep(thatisusingangular.equals).Thisway,dependingontheexpectedtypeofthereturnedbytheexpressionvalue,wecanaddlistenersinthemostappropriatewayinordertoallowtheframeworktoperformequalitycheckswiththemostoptimalalgorithmavailable.ThisAPIhastwolimitations:
Itdoesnotallowyoutochoosethemostappropriateequalitycheckalgorithmatruntime.Itdoesnotallowyoutoextendthechangedetectiontothird-partiesfortheirspecificdatastructures.
TheAngularcoreteamassignedthisresponsibilitytodiffers,allowingthemtoextendthechangedetectionmechanismandoptimizeit,basedonthedataweuseinourapplications.Angular2definestwobaseclasses,whichwecanextendinordertodefinecustomalgorithms:
KeyValueDiffer:Thisallowsustoperformadvanceddiffingoverkey-value-baseddatastructures.IterableDiffer:Thisallowsustoperformadvanceddiffingoverlist-likedatastructures.
Angular2allowsustotakefullcontroloverthechangedetectionmechanismbyextendingitwithcustomalgorithms,whichwasn’tpossibleinthepreviousversionoftheframework.We’lltakeafurtherlookintothechangedetectionandhowwecanconfigureitinChapter4,GettingStartedwithAngular2ComponentsandDirectives.
UnderstandingservicesServicesarethebuildingblocksthatAngularprovidesforthedefinitionofthebusinesslogicofourapplications.InAngularJS1.x,wehadthreedifferentwaysfordefiningservices:
//TheFactorymethod
module.factory('ServiceName',function(dep1,dep2,…){
return{
//publicAPI
};
});
//TheServicemethod
module.service('ServiceName',function(dep1,dep2,…){
//publicAPI
this.publicProp=val;
});
//TheProvidermethod
module.provider('ServiceName',function(){
return{
$get:function(dep1,dep2,…){
return{
//publicAPI
};
}
};
});
Althoughthefirsttwosyntacticalvariationsprovidesimilarfunctionality,theydifferinthewaytheregistereddirectivewillbeinstantiated.Thethirdsyntaxallowsfurtherconfigurationoftheregisteredproviderduringconfigurationtime.
HavingthreedifferentmethodsfordefiningservicesisquiteconfusingfortheAngularJS1.xbeginners.Let’sthinkforasecondwhatnecessitatedtheintroductionofthesemethodsforregisteringservices.Whycan’twesimplyuseJavaScriptconstructorfunctions,objectliterals,orES2015classesinstead,whichAngularwillnotbeawareof?WecouldencapsulateourbusinesslogicinsideacustomJavaScriptconstructorfunctionlikethis:
functionUserTransactions(id){
this.userId=id;
}
UserTransactions.prototype.makeTransaction=function(amount){
//methodlogic
};
module.controller('MainCtrl',function(){
this.submitClick=function(){
newUserTransactions(this.userId).makeTransaction(this.amount);
};
});
Thiscodeiscompletelyvalid.However,itdoesn’ttakeadvantageofoneofthekeyfeaturesthatAngularJS1.xprovides—theDImechanism.TheMainCtrlfunctionusestheconstructorfunction,UserTransaction,whichisvisibleinitsbody.Theprecedingcodehastwomainpitfalls:
We’recoupledwiththelogicusedfortheservice’sinstantiation.Thecodeisnottestable.InordertomockUserTransactions,weneedtomonkeypatchit.
HowdoesAngularJSdealwiththesetwothings?Whenagivenserviceisrequired,throughtheDImechanismoftheframework,AngularJSresolvesallofitsdependenciesandinstantiatesitbypassingthemtothefactoryfunction,whichencapsulatesthelogicforitscreation.Thefactoryfunctionispassedasthesecondargumenttothefactoryandservicemethods.Theprovidermethodallowsdefinitionofaserviceonlowerlevel;thefactorymethodthereistheoneunderthe$getpropertyoftheprovider.
JustlikeAngularJS1.x,Angular2toleratesthisseparationofconcernsaswell,sothecoreteamkepttheservices.IncontrasttoAngularJS1.x,thelastmajorversionoftheframeworkprovidesamuchsimplerinterfaceforthedefinitionofservicesbyallowingustouseplainES2015classesorES5constructorfunctions.Wecannotescapefromthefactthatweneedtoexplicitlystatewhichservicesshouldbeavailableforinjectionandsomehowspecifyinstructionsfortheirinstantiation.However,Angular2usestheES2016decorator’ssyntaxforthispurposeinsteadofthemethodsfamiliartousfromAngularJS1.x.ThisallowsustodefinetheservicesinourapplicationsassimpleasES2015classes,withdecoratorsforconfigurationoftheDI:
import{Inject,Injectable}from'angular2/core';
@Injectable()
classHttpService{
constructor(){/*…*/}
}
@Injectable()
classUser{
constructor(privateservice:HttpService){}
save(){
returnthis.service.post('/users')
.then(res=>{
this.id=res.id;
returnthis;
});
}
}
ThealternativeECMAScript5syntaxis:
varHttpService=ng.core.Class({
constructor:function(){}
});
varUser=ng.core.Class({
constructor:[HttpService,function(service){
this.service=service;
}],
save:function(){
returnthis.service.post('/users')
.then(function(res){
this.id=res.id;
returnthis;
});
}
});
Servicesarerelatedtothecomponentsandthedirectivesdescribedintheprevioussections.FordevelopinghighlycoherentandreusableUIcomponents,weneedtomoveallthebusiness-relatedlogictoinsideourservices.And,inordertodeveloptestablecomponents,weneedtotakeadvantageoftheDImechanismforresolvingalltheirdependencies.
AcoredifferencebetweentheservicesinAngular2andAngularJS1.xisthewaytheirdependenciesarebeingresolvedandrepresentedinternally.AngularJS1.xisusingstringsforidentifyingthedifferentservicesandtheassociatedfactoriesusedfortheirinstantiation.However,Angular2useskeysinstead.Usuallythekeysarethetypesofthedistinctservices.Anothercoredifferenceintheinstantiationisthehierarchicalstructureofinjectors,whichencapsulatedifferentdependencyproviderswithdifferentvisibility.
Anotherdistinctionbetweentheservicesinthelasttwomajorversionsoftheframeworkisthesimplifiedsyntax.AlthoughAngular2usesES2015classesforthedefinitionofourbusinesslogic,youcanuseECMAScript5constructorfunctionsaswellorusetheDSLprovidedbytheframework.TheDIinAngular2hasacompletelydifferentsyntaxandhasimprovedbehaviorbyprovidingaconsistentwayofinjectingdependencies.ThesyntaxusedintheprecedingexampleusesES2016decorators,andinChapter5,DependencyInjectioninAngular2,we’lltakealookatalternativesyntax,whichusesECMAScript5.YoucanalsofindmoredetailedexplanationofAngular2servicesandDIinChapter5,DependencyInjectioninAngular2.
Understandingthenewcomponent-basedrouterIntraditionalwebapplications,allthepagechangesareassociatedwithafull-pagereload,whichfetchesallofthereferencedresourcesanddataandrenderstheentirepageontothescreen.However,requirementsforwebapplicationshaveevolvedovertime.
Single-pageapplications(SPAs)thatwebuildwithAngularsimulatedesktopuserexperiences.Thisofteninvolveincrementalloadingoftheresourcesanddatarequiredbytheapplication,andnofull-pagereloadsaftertheinitialpageload.OftenthedifferentpagesorviewsinSPAsarerepresentedbydifferenttemplates,whichareloadedasynchronouslyandrenderedonaspecificpositiononthescreen.Later,whenthetemplatewithalltherequiredresourcesisloadedandtherouteischanged,thelogicattachedtotheselectedpageisinvokedandpopulatesthetemplatewithdata.IftheuserpressestherefreshbuttonafterthegivenpageinourSPAisloaded,thesamepageneedstobere-renderedaftertherefreshoftheviewcompletes.Thisinvolvessimilarbehavior—findingtherequestedview,fetchingtherequiredtemplatewithallreferencedresources,andinvokingthelogicassociatedwiththatview.
Whattemplateneedstobefetched,andthelogicwhichshouldbeinvokedafterthepagereloadssuccessfully,dependsontheselectedviewbeforetheuserpressedtherefreshbutton.TheframeworkdeterminesthisbyparsingthepageURL,whichcontainstheidentifierofthecurrentlyselectedpage,representedinthehierarchicalstructure.
Alltheresponsibilitiesrelatedtothenavigation,changingtheURL,loadingtheappropriatetemplate,andinvokingspecificlogicwhentheviewisloadedareassignedtotheroutercomponent.Thesearesomequitechallengingtasks,andsupportfordifferentnavigationAPIsrequiredforcross-browsercompatibilitymakestheimplementationofroutinginmodernSPAsanon-trivialproblem.
AngularJS1.xintroducedtherouterinitscore,whichwaslaterexternalizedintothengRoutecomponent.ItallowsadeclarativewayfordefiningthedifferentviewsinourSPA,byprovidingatemplateforeachpageandapieceoflogicthatneedstobeinvokedwhenapageisselected.However,thefunctionalityoftherouterislimited.Itdoesnotsupportessentialfeaturessuchasnestedviewrouting.That’soneofthereasonsmostdeveloperspreferredtouseui-router,developedbythecommunity.BothAngularJS1.x’srouter,andui-router,route-definitionsincludearouteconfigurationobject,whichdefinesatemplateandacontrollerassociatedwiththepage.
Asdescribedintheprevioussections,Angular2changedthebuildingblocksitprovidesforthedevelopmentofSPAs.Angular2removesthefloatingcontrollers,andinsteadrepresentsviewsasacompositionofcomponents.Thisnecessitatesthedevelopmentofabrandnewrouter,whichempowersthesenewconcepts.
ThecoredifferencesbetweentheAngularJS1.xrouterandtheAngular2routerare:
TheAngular2routeriscomponentbased,ngRouteisnot.
Thereisnowsupportfornestedviews.DifferentsyntaxempoweredbyES2016decorators.
Angular2routedefinitionsyntaxLet’stakeabrieflookatthenewsyntaxusedbytheAngular2routertodefineroutesinourapplications:
import{Component}from'angular2/core';
import{bootstrap}from'angular2/platform/browser';
import{RouteConfig,ROUTER_DIRECTIVES,ROUTER_BINDINGS}from
'angular2/router';
import{Home}from'./components/home/home';
import{About}from'./components/about/about';
@Component({
selector:'app',
templateUrl:'./app.html',
directives:[ROUTER_DIRECTIVES]
})
@RouteConfig([
{path:'/',component:Home,name:'home'},
{path:'/about',component:About,name:'about'}
])
classApp{}
bootstrap(App,[ROUTER_PROVIDERS]);
Wewon’tgointotoomuchdetailheresinceChapter6,Angular2formsandthenewcomponent-basedrouterandChapter7,Buildingareal-lifeapplicationwhileexploringpipesandhttp,arededicatedtothenewrouter,butlet’smentionthemainpointsintheprecedingcodesnippet.
Therouterlivesinthemodule,angular2/router.There,wecanfindthedirectivesitdefines,thedecoratorusedfortheconfigurationoftheroutesandROUTER_PROVIDERS.
NoteWe’lltakeafurtherlookatROUTER_PROVIDERSinChapter7,Buildingareal-lifeapplicationwhileexploringpipesandhttp.
Theparameterpassedtothe@RouteConfigdecoratorshowshowwedefinetheroutesinourapplication.Weuseanarraywithobjects,whichdefinesthemappingsbetweenroutesandthecomponentsassociatedwiththem.InsidetheComponentdecorator,weexplicitlystatethatwewanttousethedirectivescontainedwithinROUTER_DIRECTIVES,whicharerelatedtotherouter’susageinsidethetemplates.
SummaryInthischapter,wetookaquickoverviewofthemainbuildingblocksfordevelopingSPAsprovidedbyAngular2.WepointedoutthecoredifferencesbetweenthesecomponentsinAngularJS1.xandAngular2.
AlthoughwecanuseES2015,orevenES5,forbuildingAngular2applications,therecommendationfromGoogleistotakeadvantageofthelanguageusedforthedevelopmentoftheframework—TypeScript.
Inthenextchapter,we’lltakealookatTypeScriptandhowwecanstartusingitinyournextapplication.WewillalsoexplainhowwecantakeadvantageofthestatictypingintheJavaScriptlibrariesandframeworkswritteninvanillaJavaScript,withambienttypeannotations.
Chapter3.TypeScriptCrashCourseInthischapter,wearegoingtostartworkingwithTypeScript,thelanguageAngular2recommendsforscripting.AllthefeaturesECMAScript2015andrespectivelyECMAScript2016provides,suchasfunctions,classes,modules,anddecorators,arealreadyimplementedinoraddedtotheroadmapofTypeScript.Becauseoftheextratypeannotations,therearesomesyntacticaladditionscomparedtoJavaScript.
Forsmoothertransitionfromthelanguagewealreadyknow-ES5,wewillstartwithsomecommonfeaturesbetweenES2016andTypeScript.WheretherearedifferencesbetweentheESsyntaxandTypeScript,we’llexplicitlymentionit.Inthesecondhalfofthechapter,we’lladdthetypeannotationstoeverythingwe’velearneduntilthispoint.
Laterinthechapter,wewillexplaintheextrafeaturesTypeScriptprovides,suchasstatictypingandextendedsyntax.Wewilldiscussthedifferentconsequencesbasedonthesefeatures,whichwillhelpusbemoreproductiveandlesserror-prone.Let’sgetgoing!
IntroductiontoTypeScriptTypeScriptisanopensourceprogramminglanguagethatisdevelopedandmaintainedbyMicrosoft.ItsinitialpublicreleasewasinOctober2012.TypeScriptisasupersetofECMAScript,supportingallofthesyntaxandsemanticsofJavaScriptwithsomeextrafeaturesontop,suchasstatictypingandrichersyntax.
Fig.1showstherelationshipbetweenES5,ES2015,ES2016,andTypeScript.
Fig.1
BecauseTypeScriptisstaticallytyped,itcanprovideanumberofbenefitstousasJavaScriptdevelopers.Let’shaveaquicklookatthosebenefitsnow.
Compile-timetypecheckingSomeofthemostcommonmistakeswemakewhilewritingJavaScriptcodeistomisspellapropertyoramethodname.We’llfindoutaboutthemistakewhenwegetaruntimeerror.Thiscanhappenduringdevelopmentaswellasinproduction.Hopingwewillknowabouttheerrorbeforewedeployourcodetoproductionenvironmentisn’tacomfortablefeeling!However,thisisnotaproblemspecifictoJavaScript;itissomethingcommontoallthedynamiclanguages.Evenwithlotsofunittests,theseerrorscanslipby.
TypeScriptprovidesacompiler,whichtakescareofsuchmistakesforusbyusingstaticcodeanalysis.Ifwetakeadvantageofstatictyping,TypeScriptwillbeawareoftheexistingpropertiesagivenobjecthas,andifwemisspellanyofthem,thecompilerwillwarnuswithacompile-timeerror.
AnothergreatbenefitofTypeScriptisthatitallowslargeteamstocollaborate,sinceitprovidesformal,verifiablenaming.Thisway,itallowsustowriteeasy-to-understandcode.
BettersupportbytexteditorsandIDEsThereareanumberoftools,suchasTernorGoogleClosureCompiler,thataretryingtobringbetterautocompletionsupportforJavaScriptintexteditorsandIDEs.However,asJavaScriptisadynamiclanguage,itisimpossiblefortheIDEsandtexteditorstomakesophisticatedsuggestionswithoutanymetadata.
Annotatingthecodewithsuchmetadataisabuilt-infeatureofTypeScriptknownastypeannotations.Basedonthem,texteditorsandIDEscanperformbetterstaticanalysisoverourcode.Thisprovidesbetterrefactoringtoolsandautocompletion,whichincreasesourproductivityandallowsustomakefewermistakeswhilewritingthesourcecodeforourapplications.
There’sevenmoretoTypeScriptTypeScriptbyitselfhasanumberofotherbenefits:
ItisasupersetofJavaScript:AllJavaScript(ES5andES2015)programsarealreadyvalidTypeScriptones.Inessence,youhavealreadybeenwritingTypeScriptcode.SinceitisbasedonthelatestversionoftheECMAScriptstandard,itallowsustotakeadvantageofthelatestbleedingedgesyntaxprovidedbythelanguage.Supportsoptionaltypechecking:If,foranyreason,wedecidethatwedon’twanttoexplicitlydefinethetypeofavariableoramethod,wecanjustskipthetypedefinition.However,weshouldbeawarethatthismeanswearenolongertakingadvantageofthestatictyping,sowearegivinguponallthebenefitsmentionedearlier.DevelopedandmaintainedbyMicrosoft:Thequalityoftheimplementationofthelanguageisveryhighanditisunlikelythatsupportwillbedroppedunexpectedly.TypeScriptisbasedontheworkofsomeoftheworld’sbestexpertsinprogramminglanguagedevelopment.Itisopensource:Thisallowsthecommunitytofreelycontributetothelanguageandsuggestfeatures,whicharediscussedinanopenmanner.ThefactthatTypeScriptisopensourceallowsfortheeasierdevelopmentofthird-partyextensionsandtools,whichextendsfurtherthescopeofitsusage.
SincemodernbrowsersdonotsupportTypeScriptnatively,thereisacompilerthattranslatestheTypeScriptcodewewriteintoreadableJavaScriptinapredefinedtargetversionofECMAScript.Oncethecodeiscompiled,allthetypeannotationsareremoved.
UsingTypeScriptLet’sstartwritingsomeTypeScript!
Inthefollowingsections,wearegoingtotakealookatdifferentsnippetsshowingsomeofthefeaturesofTypeScript.Inordertobeabletorunthesnippetsandplaywiththemyourself,you’llneedtoinstalltheTypeScriptcompileronyourcomputer.Let’stakealookathowtodothis.
TypeScriptisbestinstalledusingNodePackageManager(npm).I’drecommendyoutousenpmVersion3.0.0ornewer.Ifyoudon’thavenode.jsandnpminstalledalready,youcanvisithttps://nodejs.organdfollowtheinstructionsthere.
InstallingTypeScriptwithnpmOnceyouhavenpminstalledandrunning,verifythatyouhavethelatestversionbyopeningyourterminalwindowandrunningthefollowingcommand:
$npm–v
InordertoinstallTypeScript1.8,use:
TheprecedingcommandwillinstalltheTypeScriptcompilerandadditsexecutable(tsc)asglobaltoyourpath.
Inordertoverifythateverythingworksproperly,youcanuse:
$tsc–v
Version1.8.0
Theoutputshouldbesimilartotheprecedingone,thoughpossiblywithadifferentversion.
RunningourfirstTypeScriptprogramNoteYoucanfindthecodeforthisbookonthefollowingURL:https://github.com/mgechev/switching-to-angular2.Asacommentinmostcodesnippetsyou’llfindarelativetotheappdirectoryfilepathwhereyoucanfindthem.
Now,let’scompileourfirstTypeScriptprogram!Createafilecalledhello.tsandenterthefollowingcontent:
//ch3/hello-world/hello-world.ts
console.log('Helloworld!');
Sinceyou’vealreadyinstalledtheTypeScriptcompiler,youshouldhaveaglobalexecutablecommandcalledtsc.Youcanuseitinordertocompilethefile:
$tschello.ts
Now,youshouldseethefilehello.jsinthesamedirectorywherehello.tsis.hello.jsistheoutputoftheTypeScriptcompiler;itcontainstheJavaScriptequivalenttotheTypeScriptyouwrote.Youcanrunthisfileusingthefollowingcommand:
$nodehello.js
Now,you’llseethestringHelloworld!printedonthescreen.Inordertocombinetheprocessofcompilingandrunningtheprogram,youcanusethepackagets-node:
$npminstall-tts-node
Nowyoucanrun:
$ts-nodehello.ts
Youshouldseethesameresult,butwithoutthets-nodefilestoredonthedisk.
TypeScriptsyntaxandfeaturesintroducedbyES2015andES2016AsTypeScriptisasupersetofJavaScript,beforewestartlearningaboutitssyntax,it’salittleeasiertostartbyintroducingsomeofthebiggerchangesinES2015andES2016;tounderstandTypeScript,wefirstmustunderstandES2015andES2016.We’regoingtohaveawhistle-stoptourthroughthesechangesbeforedivingintoTypeScriptproperlater.
AdetailedexplanationofES2015andES2016isoutsidethescopeofthisbook.Inordertogetfamiliarwithallthenewfeaturesandsyntaxes,IstronglyrecommendyoutotakealookatExploringES6:upgradetothenextversionofJavaScriptbyDr.AxelRauschmayer.
Thenextcoupleofpageswillintroducenewstandardsandallowyoutotakeadvantageofmostofthefeaturesyou’regoingtoneedinthedevelopmentofAngular2applications.
ES2015arrowfunctionsJavaScripthasfirstclassfunctions,whichmeansthattheycanbepassedaroundlikeanyothervalue:
//ch3/arrow-functions/simple-reduce.ts
varresult=[1,2,3].reduce(function(total,current){
returntotal+current;
},0);//6
Thissyntaxisgreat;however,itisabittooverbose.ES2015introducedanewsyntaxtodefineanonymousfunctionscalledthearrowfunctionsyntax.Usingit,wecancreateanonymousfunctions,asseeninthefollowingexamples:
//ch3/arrow-functions/arrow-functions.ts
//example1
varresult=[1,2,3]
.reduce((total,current)=>total+current,0);
console.log(result);
//example2
vareven=[3,1,56,7].filter(el=>!(el%2));
console.log(even);
//example3
varsorted=data.sort((a,b)=>{
vardiff=a.price-b.price;
if(diff!==0){
returndiff;
}
returna.total-b.total;
});
Inthefirstexample,wegotthetotalsumoftheelementsinthearray[1,2,3].Inthesecondexample,wegotalltheevennumbersinsidethearray[3,1,56,7].Inthethirdexample,wesortedanarraybytheproperties’priceandtotalintheascendingorder.
Arrowfunctionshaveafewmorefeaturesthatweneedtolookat.Themostimportantoneofthemisthattheykeepthecontext(this)fromthesurroundingcode:
//ch3/arrow-functions/context-demo.ts
functionMyComponent(){
this.age=42;
setTimeout(()=>{
this.age+=1;
console.log(this.age);
},100);
}
newMyComponent();//43in100ms.
Forexample,whenweinvokethefunctionMyComponentwiththeoperatornew,thiswillpointtothenewobjectinstantiatedbythecall.Thearrowfunctionwillkeepthecontext(this),inthecallbackofsetTimeout,andprint43onthescreen.
ThisisextremelyusefulinAngular2,sincethebindingcontextforagivencomponentisitsinstance(thatis,itsthis).IfwedefineMyComponentasanAngular2componentandwehaveabindingtotheageproperty,theprecedingcodewillbevalidandallthebindingswillwork(noticethatwedon’thavescope,neitherdowehaveexplicitcallstothe$digestloopalthoughwehavecalledsetTimeoutdirectly).
UsingtheES2015andES2016classesWhendevelopersnewtoJavaScripthearthatthelanguageempowerstheobject-oriented(OO)paradigm,they’renormallyconfusedwhentheydiscoverthatthere’snosyntaxforthedefinitionofclasses.Thisperceptionwasbornbythefactthatsomeofthemostpopularprogramminglanguages,suchasJava,C#,andC++,havetheconceptofclassesusedfortheconstructionofobjects.However,JavaScriptimplementstheOOparadigmdifferently.JavaScripthasaprototype-based,object-orientedprogrammingmodel,wherewecaninstantiateobjectsusingtheobjectliteralsyntaxorfunctions(alsoknownastheconstructorfunctions)andwecantakeadvantageoftheinheritanceusingtheso-calledprototypechain.
ThoughthisisavalidwaytoimplementtheOOparadigmandthesemanticsaresimilartotheoneintheclassicalobject-orientedmodel,itisconfusingforinexperiencedJavaScriptdeveloperswhoarenotsurehowtoprocessthisproperly.ThisisoneofthereasonsTC39decidedtoprovideanalternativesyntaxtoexploittheobject-orientedparadigminthelanguage.Behindthescenes,thenewsyntaxhasthesamesemanticsastheonewe’reusedto,likeusingtheconstructorfunctionsandtheprototype-basedinheritance.However,itprovidesamoreconvenientsyntaxtoempowertheOOparadigm’sfeatureswithlessboilerplate.
ES2016addssomeextrasyntaxtotheES2015classes,suchasstaticandinstancepropertydeclaration.
HereisanexamplethatdemonstratesthesyntaxusedtodefinetheclassesinES2016:
//ch3/es6-classes/sample-classes.ts
classHuman{
statictotalPeople=0;
_name;//ES2016propertydeclarationsyntax
constructor(name){
this._name=name;
Human.totalPeople+=1;
}
getname(){
returnthis._name;
}
setname(val){
this._name=val;
}
talk(){
return`Hi,I'm${this.name}!`;
}
}
classDeveloperextendsHuman{
_languages;//ES2016propertydeclarationsyntax
constructor(name,languages){
super(name);
this._languages=languages;
}
getlanguages(){
returnthis._languages;
}
talk(){
return`${super.talk()}AndIknow
${this.languages.join(',')}.`;
}
}
InES2015,theexplicitdeclarationofthe_namepropertyisnotrequired;however,sincetheTypeScriptcompilershouldbeawareduringcompile-timeoftheexistingpropertiesoftheinstancesofagivenclass,wewouldneedtoaddthedeclarationofthepropertytotheclassdeclarationitself.
TheprecedingsnippetisbothavalidTypeScriptandJavaScriptcode.Init,wedefinedaclasscalledHuman,whichaddsasinglepropertytotheobjectsinstantiatedbyit.Itdoesthisbysettingitsvaluetotheparameternamepassedtoitsconstructor.
Now,openthech3/es6-classes/sample-classes.tsfileandplayaroundwithit!Youcancreatedifferentinstancesoftheclassesthesamewayyoucreateobjectsusingconstructorfunctions:
varhuman=newHuman("foobar");
vardev=newDeveloper("bar",["JavaScript"]);
console.log(dev.talk());
Inordertoexecutethecode,runthefollowingcommand:
$ts-nodesample-classes.ts
ClassesarecommonlyusedinAngular2.Youcanusethemtodefineyourcomponents,directives,services,andpipes.However,youcanalsousethealternativeES5syntax,whichtakesadvantageoftheconstructorfunctions.Underthehood,oncetheTypeScriptcodeiscompiled,therewouldbenosuchsignificantdifferencebetweenboththesyntaxes,becausetheES2015classesarebeingtranspiledtoconstructorfunctionsanyway.
DefiningvariableswithblockscopeAnotherconfusingpointofJavaScriptfordeveloperswithadifferentbackgroundisthevariablescopeinthelanguage.InJavaandC++,forexample,we’reusedtotheblocklexicalscope.Thismeansthatagivenvariabledefinedinsideaspecificblockwillbevisibleonlyinsidethatblockandallofthenestedblocksinsideofit.
However,inJavaScript,thingsarealittlebitdifferent.ECMAScriptdefinesafunctionallexicalscopethathassimilarsemanticstotheblocklexicalscope,butitusesfunctionsinsteadofblocks.Thismeansthatwehavethefollowing:
//ch3/let/var.ts
varfns=[];
for(vari=0;i<5;i+=1){
fns.push(function(){
console.log(i);
})
}
fns.forEach(fn=>fn());
Thishassomeweirdimplications.Oncethecodehasbeenexecuted,itwilllogfivetimesthenumber5.
ES2015addedanewsyntaxtodefinethevariableswithblock-scopevisibility.Thesyntaxissimilartothecurrentone.However,insteadofvar,itusesthekeywordlet:
//ch3/let/let.ts
varfns=[];
for(leti=0;i<5;i+=1){
fns.push(function(){
console.log(i);
})
}
fns.forEach(fn=>fn());
Meta-programmingwithES2016decoratorsJavaScriptisadynamiclanguagethatallowsustoeasilymodifyand/oralterthebehaviortosuittheprogramswewrite.DecoratorsareaproposaltoES2016,whichaccordingtothedesigndocumenthttps://github.com/wycats/javascript-decorators:
“…makeitpossibletoannotateandmodifyclassesandpropertiesatdesigntime.”
TheirsyntaxesarequitesimilartotheannotationsinJava,andtheyareevenclosertothedecoratorsinPython.ES2016decoratorsareusedcommonlyinAngular2todefinecomponents,directives,andpipes,andtotakeadvantageofthedependencyinjectionmechanismoftheframework.Essentially,mostusecasesofdecoratorsinvolvealteringthebehaviortopredefinedlogicoraddingsomemetadatatodifferentconstructs.
ES2016decoratorsallowustodoalotoffancythingsbychangingthebehaviorofourprograms.Typicalusecasescouldbetoannotatethegivenmethodsorpropertiesasdeprecatedorread-only.AsetofpredefineddecoratorsthatcanimprovethereadabilityofthecodeweproducecanbefoundinaprojectbyJayPhelpscalledcore-decorators.js.Anotherusecaseistakingadvantageoftheproxy-basedaspect-orientedprogrammingusingadeclarativesyntax.Thelibraryprovidingthisfunctionalityisaspect.js.
Ingeneral,ES2016decoratorsarejustanothersyntaxsugar,whichtranslatestothecodewe’realreadyfamiliarwithfromthepreviousversionsofJavaScript.Let’stakealookatasimpleexamplefromtheproposal’sdraft:
//ch3/decorators/nonenumerable.ts
classPerson{
@nonenumerable
getkidCount(){
return42;
}
}
functionnonenumerable(target,name,descriptor){
descriptor.enumerable=false;
returndescriptor;
}
varperson=newPerson();
for(letpropinperson){
console.log(prop);
}
Inthiscase,wehaveanES2015classcalledPersonwithasinglegettercalledkidCount.OverthekidCountgetter,wehaveappliedthenonenumerabledecorator.Thedecoratorisafunctionthatacceptsatarget(thePersonclass),thenameofthetargetpropertywe
intendtodecorate(kidCount),andthedescriptorofthetargetproperty.Afterwechangethedescriptor,weneedtoreturnitinordertoapplythemodification.Basically,thedecorator’sapplicationcouldbetranslatedintoECMAScript5inthefollowingway:
descriptor=nonenumerable(Person.prototype,'kidCount',descriptor)||
descriptor;
Object.defineProperty(Person.prototype,'kidCount',descriptor);
UsingconfigurabledecoratorsHereisanexampleonusingthedecoratorsdefinedbyAngular2:
@Component({
selector:'app',
providers:[NamesList],
templateUrl:'./app.html',
directives:[RouterOutlet,RouterLink]
})
@RouteConfig([
{path:'/',component:Home,name:'home'},
{path:'/about',component:About,name:'about'}
])
exportclassApp{}
Whendecoratorsacceptarguments(justlikeComponent,RouteConfig,andViewintheprecedingexample),theyneedtobedefinedasfunctionsthatacceptargumentsandreturntheactualdecorator:
functionComponent(config){
//validateproperties
return(componentCtrl)=>{
//applydecorator
};
}
Inthisexample,wedefinedaconfigurabledecoratorcalledComponentthatacceptsasingleargumentcalledconfigandreturnsadecorator.
WritingmodularcodewithES2015AnotherproblemthatJavaScriptprofessionalshaveexperiencedalongtheyearsisthelackofamodulesysteminthelanguage.Initially,thecommunitydevelopeddifferentpatterns,aimingtoenforcethemodularityandtheencapsulationofthesoftwareweproduce.Suchpatternsincludedthemodulepattern,whichtakesadvantageofthefunctionallexicalscopeandclosures.Anotherexampleisthenamespacepattern,whichrepresentsthedifferentnamespacesasnestedobjects.AngularJS1.xintroduceditsownmodulesystemthatunfortunatelydoesn’tprovidefeatureslikelazymoduleloading.However,thesepatternsweremorelikeworkaroundsratherthanrealsolutions.
CommonJS(usedinnode.js)andAMD(AsynchronousModuleDefinition)werelaterinvented.Theyarestillinwideusetodayandprovidefeatures,suchashandlingofcirculardependencies,asynchronousmoduleloading(inAMD),andsoon.
TC39tookthebestoftheexistingmodulesystemsandintroducedthisconceptonalanguagelevel.ES2015providestwoAPIstodefineandconsumemodules.Theyareasfollows:
DeclarativeAPI.ImperativeAPIusingthemoduleloader.
Angular2takesfulladvantageoftheES2015modulesystem,solet’sdiveintoit!Inthissection,wearegoingtotakealookatthesyntaxusedforthedeclarativedefinitionandconsumptionofmodules.Wearealsogoingtopeekatthemoduleloader’sAPIinordertoseehowwecanprogrammaticallyloadmodulesinanexplicitasynchronousmanner.
UsingtheES2015modulesyntaxLet’stakealookatanexample:
//ch3/modules/math.ts
exportfunctionsquare(x){
returnMath.pow(x,2);
};
exportfunctionlog10(x){
returnMath.log10(x);
};
exportconstPI=Math.PI;
Intheprecedingsnippet,wedefinedasimpleES2015moduleinthefilemath.ts.WecanthinkofitasasamplemathAngular2utilitymodule.Insideit,wedefinedandexportedthefunctionssquareandlog10,andtheconstantPI.TheconstkeywordisanotherkeywordbroughtbyES2015thatisusedtodefineconstants.Asyoucansee,whatwedoisnothingmorethanprefixingthefunction’sdefinitionswiththekeywordexport.Ifwewanttoexporttheentirefunctionalityintheendandskiptheduplicateexplicitusageofexport,wecan:
//ch3/modules/math2.ts
functionsquare(x){
returnMath.pow(x,2);
};
functionlog10(x){
returnMath.log10(x);
};
constPI=Math.PI;
export{square,log10,PI};
Thesyntaxonthelastlineisnothingmorethananenhancedobjectliteralsyntax,introducedbyES2015.Now,let’stakealookathowwecanconsumethismodule:
//ch3/modules/app.ts
import{square,log10}from'./math';
console.log(square(2));//4
console.log(log10(10));//1
Asanidentifierofthemodule,weuseditsrelativepathtothecurrentfile.Byusingdestructuring,weimportedtherequiredfunctions—inthiscase,squareandlog10.
TakingadvantageoftheimplicitasynchronousbehaviorItisimportanttonotethattheES2015modulesyntaxhasimplicitasynchronousbehavior.
Let’ssaywehavemodulesA,B,andC.ModuleAusesmodulesBandC,soitdependsonthem.OncetheuserrequiresmoduleA,theJavaScriptmoduleloaderwouldneedtoloadmodulesBandCbeforebeingabletoinvokeanyofthelogicthatresidesinmoduleAbecauseofthedependencieswehave.However,modulesBandCwillbeloadedasynchronously.Oncetheyareloadedcompletely,theJavaScriptvirtualmachinewillbeabletoexecutemoduleA.
UsingaliasesAnothertypicalsituationiswhenwewanttouseanaliasforagivenexport.Forexample,ifweuseathird-partylibrary,wemaywanttorenameanyofitsexportsinordertoescapenamecollisionsorjusttohaveamoreconvenientnaming:
import{bootstrapasinitialize}from'angular2/platform/browser';
ImportingallthemoduleexportsWecanimporttheentiremathmoduleusing:
//ch3/modules/app2.ts
import*asmathfrom'./math';
console.log(math.square(2));//4
console.log(math.log10(10));//1
console.log(math.PI);//3.141592653589793
ThesemanticsbehindthissyntaxisquitesimilartoCommonJS,althoughinthebrowser,wehaveimplicitasynchronousbehavior.
DefaultexportsIfagivenmoduledefinesanexport,whichwouldquitelikelybeusedbyanyofitsconsumermodules,wecantakeadvantageofthedefaultexportsyntax:
//ch3/modules/math3.ts
exportdefaultfunctioncube(x){
returnMath.pow(x,3);
};
exportfunctionsquare(x){
returnMath.pow(x,2);
};
Inordertoconsumethismodule,wecanusethefollowingapp.tsfile:
//ch3/modules/app3.ts
importcubefrom'./math3';
console.log(cube(3));//27
Or,ifwewanttoimportthedefaultexportaswellassomeotherexports,wecanuse:
//ch3/modules/app4.ts
importcube,{square}from'./math3';
console.log(square(2));//4
console.log(cube(3));//27
Ingeneral,thedefaultexportisnothingmorethananamedexportnamedwiththereservedworddefault:
//ch3/modules/app5.ts
import{defaultascube}from'./math3';
console.log(cube(3));//27
ES2015moduleloaderThenewversionofthestandarddefinesaprogrammaticAPItoworkwithmodules.Thisistheso-calledmoduleloaderAPI.Itallowsustodefineandimportmodules,orconfigurethemoduleloading.
Let’ssupposewehavethefollowingmoduledefinitioninthefileapp.js:
import{square}from'./math';
exportfunctionmain(){
console.log(square(2));//4
}
Fromthefileinit.js,wecanprogrammaticallyloadtheappmoduleandinvokeitsmainfunctionusing:
System.import('./app')
.then(app=>{
app.main();
})
.catch(error=>{
console.log('Terribleerrorhappened',error);
});
TheglobalobjectSystemhasamethodcalledimportthatallowsustoimportmodulesusingtheiridentifier.Intheprecedingsnippet,weimportedthemoduleappdefinedinapp.js.System.importreturnsapromisethatcouldberesolvedonsuccessorrejectedincaseofanerror.Oncethepromiseisresolvedasthefirstparameterofthecallbackpassedtothen,wewillgetthemoduleitself.Thefirstparameterofthecallbackregisteredincaseofrejectionistheerrorthathappened.
ThecodefromthelastsnippetdoesnotexistintheGitHubrepository,sinceitrequiressomeadditionalconfiguration.WearegoingtoapplythemoduleloadermoreexplicitlyintheAngular2examplesinthenextchaptersofthebook.
ES2015andES2016recapCongratulations!We’remorethanhalfwaytowardlearningTypeScript.Allthefeatureswe’vejustseenareapartofTypeScript,sinceitimplementsasupersetofJavaScriptandsinceallthesefeaturesareanupgradeontopofthecurrentsyntax,theyareeasytograspbyexperiencedJavaScriptdevelopers.
Inthenextsections,wewilldescribealltheamazingfeaturesofTypeScriptthatareoutsidetheintersectionwithECMAScript.
TakingadvantageofstatictypingStatictypingiswhatcanprovidebettertoolingforourdevelopmentprocess.WhilewritingJavaScript,themostthatIDEsandtexteditorscandoissyntaxhighlightingandprovidingsomebasicautocompletionsuggestionsbasedonthesophisticatedstaticanalysisofourcode.Thismeansthatwecanonlyverifythatwehaven’tmadeanytyposbyrunningthecode.
Intheprevioussections,wedescribedonlythenewfeaturesprovidedbyECMAScriptexpectedtobeimplementedbybrowsersinthenearfuture.Inthissection,wewilltakealookatwhatTypeScriptprovidesinordertohelpusbelesserror-proneandmoreproductive.Atthetimeofthiswriting,there’renoplanstoimplementbuilt-insupportforstatictypinginthebrowsers.
TheTypeScriptcodegoesthroughintermediatepreprocessingthatperformsthetypecheckinganddropsallthetypeannotationsinordertoprovidevalidJavaScriptsupportedbymodernbrowsers.
UsingexplicittypedefinitionsJustlikeJavaandC++,TypeScriptallowsustoexplicitlydeclarethetypeofthegivenvariable:
letfoo:number=42;
Theprecedinglinedefinesthevariablefoointhecurrentblockusingtheletsyntax.Weexplicitlydeclarethatwewantfootobeofthetypenumberandwesetthevalueoffooto42.
Nowlet’strytochangethevalueoffoo:
letfoo:number=42;
foo='42';
Here,afterthedeclarationoffoo,wewillsetitsvaluetothestring'42'.ThisisaperfectlyvalidJavaScriptcode;however,ifwecompileitusingtheTypeScript’scompiler,wewillget:
$tscbasic.ts
basic.ts(2,1):errorTS2322:Type'string'isnotassignabletotype
'number'.
Oncefoohasbeenassociatedwiththegiventype,wecannotassignitvaluesbelongingtodifferenttypes.Thisisoneofthereasonswecanskiptheexplicittypedefinitionincaseweassignavaluetothegivenvariable:
letfoo=42;
foo='42';
ThesemanticsbehindthiscodewillbethesameastheonewiththeexplicittypedefinitionbecauseofthetypeinferenceofTypeScript.We’llfurthertakealookatitattheendofthischapter.
ThetypeanyAllthetypesinTypeScriptaresubtypesofatypecalledany.Wecandeclarevariablesbelongingtotheanytypebyusingtheanykeyword.Suchvariablescanholdthevalueofanytype:
letfoo:any;
foo={};
foo='bar';
foo+=42;
console.log(foo);//"bar42"
TheprecedingcodeisavalidTypeScript,anditwillnotthrowanyerrorduringcompilationorruntime.Ifweusethetypeanyforallofourvariables,wewillbebasicallywritingthecodewithdynamictyping,whichdropsallthebenefitsoftheTypeScript’scompiler.That’swhywehavetobecarefulwithanyanduseitonlywhenitisnecessary.
AlltheothertypesinTypeScriptbelongtooneofthefollowingcategories:
Primitivetypes:ThisincludesNumber,String,Boolean,Void,Null,Undefined,andEnumtypes.Uniontypes:Uniontypesareoutofthescopeofthisbook.YoucantakealookattheminthespecificationofTypeScript.Objecttypes:ThisincludesFunctiontypes,classesandinterfacetypereferences,arraytypes,tupletypes,functiontypes,andconstructortypes.Typeparameters:ThisincludesGenericsthataregoingtobedescribedintheWritinggenericcodebyusingtypeparameterssection.
UnderstandingthePrimitivetypesMostoftheprimitivetypesinTypeScriptaretheoneswearealreadyfamiliarwithinJavaScript:Number,String,Boolean,Null,andUndefined.So,wearegoingtoskiptheirformalexplanationhere.AnothersetoftypesthatishandywhiledevelopingAngular2applicationsistheEnumtypesdefinedbyusers.
TheEnumtypesTheEnumtypesareprimitiveuser-definedtypesthat,accordingtothespecification,aresubclassesofNumber.TheconceptofenumsexistsintheJava,C++,andC#languages,andithasthesamesemanticsinTypeScript—user-definedtypesconsistingofsetsofnamedvaluescalledelements.InTypeScript,wecandefineenumusingthefollowingsyntax:
enumSTATES{
CONNECTING,
CONNECTED,
DISCONNECTING,
WAITING,
DISCONNECTED
};
ThisisgoingtobetranslatedtothefollowingJavaScript:
varSTATES;
(function(STATES){
STATES[STATES["CONNECTING"]=0]="CONNECTING";
STATES[STATES["CONNECTED"]=1]="CONNECTED";
STATES[STATES["DISCONNECTING"]=2]="DISCONNECTING";
STATES[STATES["WAITING"]=3]="WAITING";
STATES[STATES["DISCONNECTED"]=4]="DISCONNECTED";
})(STATES||(STATES={}));
Wecanusetheenumtypeasfollows:
if(this.state===STATES.CONNECTING){
console.log('Thesystemisconnecting');
}
UnderstandingtheObjecttypesInthissection,we’regoingtotakealookattheArraytypesandFunctiontypes,whichbelongtothemoregenericclassofObjecttypes.Wewillalsoexplorehowwecandefineclassesandinterfaces.TupletypeswereintroducedbyTypeScript1.3,andtheirmainpurposeistoallowthelanguagetobegintypingthenewfeaturesintroducedbyES2015,suchasdestructuring.Wewillnotdescribetheminthisbook.Forfurtherreadingyoucantakealookatthelanguage’sspecificationathttp://www.typescriptlang.org.
TheArraytypesInTypeScript,arraysareJavaScriptarrayswithacommonelementtype.Thismeansthatwecannothaveelementsfromdifferenttypesinagivenarray.Wehavedifferentarraytypesforallthebuilt-intypesinTypeScript,plusallthecustomtypesthatwedefine.
Wecandefineanarrayofnumbersasfollows:
letprimes:number[]=[];
primes.push(2);
primes.push(3);
Ifwewanttohaveanarray,whichseemsheterogeneous,similartothearraysinJavaScript,wecanusethetypereferencetoany:
letrandomItems:any[]=[];
randomItems.push(1);
randomItems.push("foo");
randomItems.push([]);
randomItems.push({});
Thisispossible,sincethetypesofallthevalueswe’repushingtothearrayaresubtypesoftheanytypeandthearraywe’vedeclaredcontainsvaluesofthetypeany.
Wecanusethearraymethodswe’refamiliarwithinJavaScriptwithalltheTypeScriptArraytypes:
letrandomItems:any[]=[];
randomItems.push("foo");
randomItems.push("bar");
randomItems.join('');//foobar
randomItems.splice(1,0,"baz");
randomItems.join('');//foobazbar
Wealsohavethesquare-bracketsoperatorthatgivesusrandomaccesstothearray’selements:
letrandomItems:any[]=[];
randomItems.push("foo");
randomItems.push("bar");
randomItems[0]==="foo"
randomItems[1]==="bar"
TheFunctiontypes
Thefunctiontypesareasetofallthefunctionswithdifferentsignatures,includingthedifferentnumberofarguments,differentarguments’types,ordifferenttypesofthereturnresult.
We’realreadyfamiliarwithhowtocreateanewfunctioninJavaScript.Wecanusefunctionexpressionorfunctiondeclaration:
//functionexpression
varisPrime=function(n){
//body
};
//functiondeclaration
functionisPrime(n){
//body
};
Or,wecanusethenewarrowfunctionsyntax:
varisPrime=n=>{
//body
};
TheonlythingTypeScriptaltersisthefeaturetodefinethetypesofthefunction’sargumentsandthetypeofitsreturnresult.Afterthecompilerofthelanguageperformsitstypecheckingandtranspilation,allthetypeannotationswillberemoved.Ifweusefunctionexpressionandweassignafunctiontoavariable,wewillbeabletodefinethevariabletypeinthefollowingway:
letvariable:(arg1:type1,arg2:type2,…,argn:typen)=>returnType
Forexample:
letisPrime:(n:number)=>boolean=n=>{
//body
};
Incaseoffunctiondeclaration,we’llhave:
functionisPrime(n:number):boolean{
//body
}
Ifwewanttodefineamethodinaobjectliteral,wecanprocessitinthefollowingway:
letmath={
squareRoot(n:number):number{
//…
},
};
Intheprecedingexample,wedefinedanobjectliteralusingtheES2015syntaxthatdefinesthemethodsquareRoot.
Incasewewanttodefineafunctionthatproducessomesideeffectsinsteadofreturningaresult,wecandefineitasavoidfunction:
letperson={
_name:null,
setName(name:string):void{
this._name=name;
}
};
DefiningclassesTypeScriptclassesaresimilartowhatES2015offers.However,italtersthetypedeclarationsandcreatesmoresyntaxsugar.Forexample,let’staketheHumanclasswedefinedearlierandmakeitavalidTypeScriptclass:
classHuman{
statictotalPeople=0;
_name:string;
constructor(name){
this._name=name;
Human.totalPeople+=1;
}
getname(){
returnthis._name;
}
setname(val){
this._name=val;
}
talk(){
return`Hi,I'm${this.name}!`;
}
}
ThereisnodifferencebetweenthecurrentTypeScriptdefinitionwiththeonewealreadyintroduced,however,inthiscasethedeclarationofthe_namepropertyismandatory.Hereishowwecanusetheclass:
lethuman=newHuman('foo');
console.log(human._name);
UsingaccessmodifiersSimilarly,formostconventionalobject-orientedlanguagesthatsupportclasses,TypeScriptallowsdefinitionofaccessmodifiers.Inordertodenydirectaccesstothe_namepropertyoutsidetheclassitisdefinedin,wecandeclareitasprivate:
classHuman{
statictotalPeople=0;
private_name:string;
//…
}
ThesupportedaccessmodifiersbyTypeScriptare:
Public:Allthepropertiesandmethodsdeclaredaspubliccouldbeaccessedanywhere.Private:Allthepropertiesandmethodsdeclaredasprivatecanbeaccessedonlyfrominsidetheclass’definitionitself.Protected:Allthepropertiesandmethodsdeclaredasprotectedcanbeaccessedfrominsidetheclass’definitionorthedefinitionofanyotherclassextendingtheonethatownsthepropertyorthemethod.
AccessmodifiersareagreatwaytoimplementAngular2serviceswithgoodencapsulationandawell-definedinterface.Inordertounderstanditbetter,let’stakealookatanexampleusingtheclass’hierarchydefinedearlier,whichisportedtoTypeScript:
classHuman{
statictotalPeople=0;
constructor(protectedname:string,privateage:number){
Human.totalPeople+=1;
}
talk(){
return`Hi,I'm${this.name}!`;
}
}
classDeveloperextendsHuman{
constructor(name:string,privatelanguages:string[],age:number){
super(name,age);
}
talk(){
return`${super.talk()}AndIknow${this.languages.join(',')}.`;
}
}
JustlikeES2015,TypeScriptsupportstheextendskeywordanddesugarsittotheprototypalJavaScriptinheritance.
Intheprecedingexample,wesettheaccessmodifiersofthenameandagepropertiesdirectlyinsidetheconstructorfunction.Thesemanticsbehindthissyntaxdiffersfromtheoneusedinthepreviousexample.Ithasthefollowingmeaning:defineaprotected
propertycallednameofthetypestringandassignthefirstvaluepassedtotheconstructorcalltoit.Itisthesamefortheprivateageproperty.Thissavesusfromexplicitlysettingthevalueintheconstructoritself.IfwetakealookattheconstructoroftheDeveloperclass,wecanseethatwecanusethemixturebetweenthesesyntaxes.Wecanexplicitlydefinethepropertyintheconstructor’ssignatureorwecanonlydefinethattheconstructoracceptstheparametersofthegiventypes.
Now,let’screateanewinstanceoftheDeveloperclass:
letdev=newDeveloper("foo",["JavaScript","Go"],42);
dev.languages=["Java"];
Duringcompilation,TypeScriptwillthrowanerrortellingusthatPropertylanguagesisprivateandonlyaccessibleinsideclass“Developer”.Now,let’sseewhat’sgoingtohappenifwecreateanewHumanclassandtrytoaccessitspropertiesfromoutsideitsdefinition:
lethuman=newHuman("foo",42);
human.age=42;
human.name="bar";
Inthiscase,we’llgetthefollowingtwoerrors:
Propertyageisprivateandisonlyaccessibleinsideclass“Human”andthePropertynameisaprotectedandonlyaccessibleinsideclass“Human”anditssubclasses.
However,ifwetrytoaccessthe_namepropertyfrominsidethedefinitionofDeveloper,thecompilerwon’tthrowanyerrors.
InordertogetabettersenseofwhattheTypeScriptcompilerwillproduceoutofatypeannotatedclass,let’stakealookattheJavaScriptproducedbythefollowingdefinition:
classHuman{
constructor(privatename:string){}
}
TheresultingECMAScript5willbe:
varHuman=(function(){
functionHuman(name){
this.name=name;
}
returnHuman;
})();
Thedefinedpropertyisaddeddirectlytotheobjectsinstantiatedbycallingtheconstructorfunctionwiththeoperatornew.Thismeansthatoncethecodeiscompiled,wecandirectlyaccesstheprivatemembersofthecreatedobjects.Inordertowrapthisup,accessmodifiersareaddedinthelanguageinordertohelpusenforcebetterencapsulationandgetcompile-timeerrorsincaseweviolateit.
DefininginterfacesSubtypinginprogramminglanguagesallowsustotreatobjectsinthesamewaybasedontheobservationthattheyarespecializedversionsofagenericobject.Thisdoesn’tmeanthattheyhavetobeinstancesofthesameclassofobjects,orthattheyhavecompleteintersectionbetweentheirinterfaces.Theobjectsmighthaveonlyafewcommonpropertiesandstillbetreatedthesamewayinaspecificcontext.InJavaScript,weusuallyuseducktyping.Wemayinvokespecificmethodsforalltheobjectspassedtoafunctionbasedontheassumptionthatthesemethodsexist.However,allofushaveexperiencedtheundefinedisnotafunctionerrorthrownbytheJavaScriptinterpreter.
Object-orientedprogrammingandTypeScriptcomewithasolution.Theyallowustomakesureourobjectshavesimilarbehavioriftheyimplementinterfacesthatdeclarethesubsetofthepropertiestheyown.
Forexample,wecandefineourinterfaceAccountable:
interfaceAccountable{
getIncome():number;
}
Now,wecanmakesurebothIndividualandFirmimplementthisinterfacebydoingasfollows:
classFirmimplementsAccountable{
getIncome():number{
//…
}
}
classIndividualimplementsAccountable{
getIncome():number{
//…
}
}
Incaseweimplementagiveninterface,weneedtoprovideimplementationforallthemethodsdefinedinsideit,otherwisetheTypeScriptcompilerwillthrowanerror.Themethodsweimplementmusthavethesamesignatureastheonesdeclaredintheinterfacedefinition.
TypeScriptinterfacesalsosupportproperties.IntheAccountableinterface,wecanincludeafieldcalledaccountNumberwithatypeofstring:
interfaceAccountable{
accountNumber:string;
getIncome():number;
}
Wecandefineitinourclassasafieldoragetter.
InterfaceinheritanceInterfacesmayalsoextendeachother.Forexample,wemayturnourIndividualclass
intoaninterfacethathasasocialsecuritynumber:
interfaceAccountable{
accountNumber:string;
getIncome():number;
}
interfaceIndividualextendsAccountable{
ssn:string;
}
Sinceinterfacessupportmultipleinheritances,IndividualmayalsoextendtheinterfaceHumanthathasthenameandageproperties:
interfaceAccountable{
accountNumber:string;
getIncome():number;
}
interfaceHuman{
age:number;
name:number;
}
interfaceIndividualextendsAccountable,Human{
ssn:string;
}
ImplementingmultipleinterfacesIncasetheclass’sbehaviorisaunionofthepropertiesdefinedinacoupleofinterfaces,itmayimplementallofthem:
classPersonimplementsHuman,Accountable{
age:number;
name:string;
accountNumber:string;
getIncome():number{
//...
}
}
Inthiscase,weneedtoprovidetheimplementationofallthemethodsdeclaredinsidetheinterfacesourclassimplements,otherwisethecompilerwillthrowacompile-timeerror.
FurtherexpressivenesswithTypeScriptdecoratorsInES2015,weareabletodecorateonlyclasses,properties,methods,getters,andsetters.TypeScripttakesthisfurtherbyallowingustodecoratefunctionsormethodparameters:
classHttp{
//…
}
classGitHubApi{
constructor(@Inject(Http)http){
//…
}
}
However,theparameterdecoratorsshouldnotalteranyadditionalbehavior.Instead,theyareusedtogeneratemetadata.ThemosttypicalusecaseofthesedecoratorsisthedependencyinjectionmechanismofAngular2.
WritinggenericcodebyusingtypeparametersInthebeginningofthesectiononusingstatictyping,wementionedthetypeparameters.Inordertogetabetterunderstandingofthem,let’sbeginwithanexample.Let’ssupposewewanttoimplementtheclassicaldata-structureBinarySearchTree.Let’sdefineitsinterfaceusingaclasswithoutapplyinganymethodimplementations:
classNode{
value:any;
left:Node;
right:Node;
}
classBinarySearchTree{
privateroot:Node;
insert(any:value):void{/*…*/}
remove(any:value):void{/*…*/}
exists(any:value):boolean{/*…*/}
inorder(callback:{(value:any):void}):void{/*…*/}
}
Intheprecedingsnippet,wedefinedaclasscalledNode.Theinstancesofthisclassrepresenttheindividualnodesinourtree.Eachnodehasaleftandarightchildnodeandavalueofthetypeany;weuseanyinordertobeabletostoredataofanytypeinsideournodesandrespectivelyinsideBinarySearchTree.
Althoughtheearlierimplementationlooksreasonable,we’regivinguponusingthemostimportantfeaturethatTypeScriptprovides—statictyping.ByusinganyasatypeofthevaluefieldinsidetheNodeclass,wecan’ttakecompleteadvantageofthecompile-timetypechecking.ThisalsolimitsthefeaturesthatIDEsandtexteditorsprovidewhenweaccessthevaluepropertyoftheinstancesoftheNodeclass.
TypeScriptcomeswithanelegantsolutionthatisalreadywidelypopularintheworldofstatictyping—typeparameters.Usinggenerics,wecanparameterizetheclasseswecreatewiththetypeparameters.Forexample,wecanturnourNodeclassintothefollowing:
classNode<T>{
value:T;
left:Node<T>;
right:Node<T>;
}
Node<T>indicatesthatthisclasshasasingletypeparametercalledTthatisusedsomewhereinsidetheclass’sdefinition.WecanuseNodebydoingasfollows:
letnumberNode=newNode<number>();
letstringNode=newNode<string>();
numberNode.right=newNode<number>();
numberNode.value=42;
numberNode.value="42";//Type"string"isnotassignabletotype
"number"
numberNode.left=stringNode;//TypeNode<string>isnotassignableto
typeNode<number>
Intheprecedingsnippet,wecreatedthreenodes:numberNode,stringNode,andanothernodeofthetypeNode<number>,assigningitsvaluetotherightchildofnumberNode.NoticethatsincenumberNodeisofthetypeNode<number>,wecansetitsvalueto42,butwecan’tusethestring"42".Thesameisapplicabletoitsleftchild.Inthedefinition,we’veexplicitlydeclaredthatwewanttheleftandrightchildrentobeofthetypeNode<number>.ThismeansthatwecannotassignvaluesofthetypeNode<string>tothem;that’swhywegetthesecondcompile-timeerror.
UsinggenericfunctionsAnothertypicaluseofgenericsisfordefiningfunctionsthatoperateoverasetoftypes.Forexample,wemaydefineanidentityfunctionthatacceptsanargumentoftypeTandreturnsit:
functionidentity<T>(arg:T){
returnarg;
}
However,insomecases,wemaywanttouseonlytheinstancesofthetypesthathavesomespecificproperties.Forachievingthis,wecanuseanextendedsyntaxthatallowsustodeclaresubtypesofthetypesthatshouldbethetypeparameters:
interfaceComparable{
compare(a:Comparable):number;
}
functionsort<TextendsComparable>(arr:Comparable[]):Comparable[]{
//…
}
Forexample,here,wedefinedaninterfacecalledComparable.Ithasasingleoperationcalledcompare.TheclassesthatimplementtheinterfaceComparableneedtoimplementtheoperationcompare.Whencompareiscalledwithagivenargument,itreturns1ifthetargetobjectisbiggerthanthepassedargument,0iftheyareequal,and-1ifthetargetobjectissmallerthanthepassedargument.
HavingmultipletypeparametersTypeScriptallowsustousemultipletypeparameters:
classPair<K,V>{
key:K;
value:V;
}
Inthiscase,wecancreateaninstanceoftheclassPair<K,V>usingthefollowingsyntax:
letpair=newPair<string,number>();
pair.key="foo";
pair.value=42;
WritinglessverbosecodewithTypeScript’stypeinferenceStatictypinghasanumberofbenefits;however,itmakesuswriteamoreverbosecodebyaddingalltherequiredtypeannotations.
Insomecases,theTypeScript’scompilerisabletoguessthetypesofexpressionsinsideourcode,forinstance:
letanswer=42;
answer="42";//Type"string"isnotassignabletotype"number"
Intheprecedingexample,wedefinedavariableanswerandweassignedthevalue42toit.SinceTypeScriptisstaticallytypedandthetypeofavariablecannotchangeoncedeclared,thecompilerissmartenoughtoguessthatthetypeofanswerisnumber.
Ifwedon’tassignavaluetoavariablewithinitsdefinition,thecompilerwillsetitstypetoany:
letanswer;
answer=42;
answer="42";
Theprecedingsnippetwillcompilewithoutanycompile-timeerrors.
BestcommontypeSometimes,thetypeinferencecouldbearesultofseveralexpressions.Suchisthecasewhenweassignaheterogeneousarraytoavariable:
letx=["42",42];
Inthiscase,thetypeofxwillbeany[].However,supposewehavethefollowing:
letx=[42,null,32];
Thetypeofxwillthenbenumber[],sincethetypeNumberisasubtypeofNull.
ContextualtypeinferenceContextualtypingoccurswhenthetypeofanexpressionisimpliedfromitslocation,forexample:
document.body.addEventListener("mousedown",e=>{
e.foo();//Property"foo"doesnotexistsonatype"MouseEvent"
},false);
Inthiscase,thetypeoftheargumentofthecallbackeisguessedbythecompilerbasedonthecontextinwhichitisused.ThecompilerunderstandswhatthetypeofeisbasedonthecallofaddEventListenerandtheargumentspassedtothemethod.Incasewewereusingakeyboardevent(keydown,forexample),TypeScriptwouldhavebeenawarethateisofthetypeKeyboardEvent.
TypeinferenceisamechanismthatallowsustowritelessverbosecodebytakingadvantageofthestaticanalysisperformedbyTypeScript.Basedonthecontext,TypeScript’scompilerisabletoguessthetypeofagivenexpressionwithoutexplicitdefinition.
UsingambienttypedefinitionsAlthoughstatictypingisamazing,mostofthefrontendlibrariesweusearebuiltwithJavaScript,whichisdynamicallytyped.Sincewe’dwanttouseTypeScriptinAngular2,nothavingcompile-typinginthecodethatusesexternallibrariesisabigissue;itpreventsusfromtakingadvantageofthecompile-timetype-checking.
TypeScriptwasbuiltkeepingthesepointsinmind.InordertoallowtheTypeScriptcompilertotakecareofwhatitdoesbest,wecanusetheso-calledambienttypedefinitions.TheyallowustoprovideexternaltypedefinitionsoftheexistingJavaScriptlibraries.Thisway,theyprovidehintstothecompiler.
UsingpredefinedambienttypedefinitionsFortunately,wedon’thavetocreateambienttypedefinitionsforallJavaScriptlibrariesandframeworksweuse.Thecommunityand/ortheauthorsoftheselibrarieshavealreadypublishedsuchdefinitionsonline;thebiggestrepositoryresidesat:https://github.com/DefinitelyTyped/DefinitelyTyped.There’salsoatoolformanagingthemcalledtypings.Wecaninstallitusingnpmbythefollowingcommand:
npminstall–gtypings
Theconfigurationoftypingsisdefinedinafilecalledtypings.jsonandallinstalledambienttypings,bydefault,willbeinthedirectory./typings.
Inordertocreatetypings.jsonfilewithbasicconfigurationuse:
typingsinit
Wecaninstallnewtypedefinitionusing:
typingsinstallangularjs--ambient
TheprecedingcommandwilldownloadthetypedefinitionsforAngularJS1.xandsavetheminbothbrowser/ambient/angular/angular.d.tsandmain/ambient/angular/angular.d.tsunderthetypingsdirectory.
NoteHavingbothmain/ambientandbrowser/ambientdirectoriesisduetopreventingtypecollisions.Forinstance,ifweuseTypeScriptinboththebackend/buildofourproject,anditsfrontendtherecouldbeintroducedduplicationsoftypedefinitionswhichwillleadtocompile-timeerrors.Byhavingtwodirectoriesfortheambienttypingsoftheindividualpartsoftheproject,wecanincludeonlyoneofthemusingrespectivelymain.d.tsandbrowser.d.ts.ForfurtherinformationontypingsyoucanvisittheofficialrepositoryoftheprojectonGitHubhttps://github.com/typings/typings.
Inordertodownloadatypedefinitionandaddentryforitinsidetypings.jsonyoucanuse:
typingsinstallangular--ambient--save
Afterrunningtheprecedingcommandyourtypings.jsonfileshouldlooksimilarto:
{
"dependencies":{},
"devDependencies":{},
"ambientDependencies":{
"angular":
"github:DefinitelyTyped/DefinitelyTyped/angularjs/angular.d.ts#1c4a34873c9e
70cce86edd0e61c559e43dfa5f75"
}
}
NowinordertouseAngularJS1.xwithTypeScriptcreateapp.tsandenterthefollowingcontent:
///<referencepath="./typings/browser.d.ts"/>
varmodule=angular.module("module",[]);
module.controller("MainCtrl",
functionMainCtrl($scope:angular.IScope){
});
Tocompileapp.tsuse:
tscapp.ts
TheTypeScriptcompilewilloutputthecompiledcontentintoapp.js.InordertoaddextraautomationandinvoketheTypeScriptcompilereachtimeyouchangeanyofthefilesinyourproject,youcanuseataskrunnerlikegulporgrunt,orpassthe-woptiontotsc.
NoteSinceusingthereferenceelementforincludingtypedefinitionsisconsideredbadpracticewecanuseatsconfig.jsonfileinstead.Therewecanconfigurewhichdirectoriesneedtobeincludedinthecompilationprocessbytsc.Formoreinformationvisithttps://github.com/Microsoft/TypeScript/wiki/tsconfig.json.
CustomambienttypedefinitionsTounderstandhoweverythingworkstogether,let’stakealookatanexample.SupposewehavethefollowinginterfaceofaJavaScriptlibrary:
varDOM={
//Returnsasetofelementswhichmatchthepassedselector
selectElements:function(selector){
//…
},
hide:function(element){
//…
},
show:function(element){
//…
}
};
WehaveanobjectliteralassignedtoavariablecalledDOM.Theobjecthasthefollowingmethods:
selectElements:AcceptsasingleargumentwithtypestringandreturnsasetofDOMelements.hide:AcceptsaDOMnodeasanargumentandreturnsnothing.show:AcceptsaDOMnodeasanargumentandreturnsnothing.
InTypeScript,theprecedingdefinitionwouldlookasfollows:
varDOM={
//Returnsasetofelementswhichmatchthepassedselector
selectElements:function(selector:string):HTMLElement[]{
return[];
},
hide:function(element:HTMLElement):void{
element.hidden=true;
},
show:function(element:HTMLElement):void{
element.hidden=false;
}
};
Thismeansthatwecandefineourlibrary’sinterfaceasfollows:
interfaceLibraryInterface{
selectElements(selector:string):HTMLElement[]
hide(element:HTMLElement):void
show(element:HTMLElement):void
}
Definingts.dfilesAfterwehavetheinterfaceofourlibrary,itwillbeeasytocreatetheambienttypedefinition;wejusthavetocreateafilewithanextensionts.dcalleddomandenterthefollowingcontent:
//inside"dom.d.ts"
interfaceDOMLibraryInterface{
selectElements(selector:string):HTMLElement[]
hide(element:HTMLElement):void
show(element:HTMLElement):void
}
declarevarDOM:DOMLibraryInterface;
Intheprecedingsnippet,wedefinedtheinterfacecalledDOMLibraryInterfaceanddeclaredthevariableDOMofthetypeDOMLibraryInterface.
TheonlythingleftbeforebeingabletoexploitstatictypingwithourJavaScriptlibraryisincludingtheexternaltypedefinitioninthescriptfileswewanttouseourlibraryin.Wecandoitasfollows:
///<referencepath="dom.d.ts"/>
Theprecedingsnippethintsthecompileronwheretofindtheambienttypedefinitions.
SummaryInthischapter,wepeekedattheTypeScriptlanguagethatisusedfortheimplementationofAngular2.AlthoughwecandevelopourAngular2applicationsusingECMAScript5,Google’srecommendationistouseTypeScriptinordertotakeadvantageofthestatictypingitprovides.
Whileexploringthelanguage,welookedatsomeofthecorefeaturesofES2015andES2016.WeexplainedtheES2015andES2016classes,arrowfunctions,blockscopevariabledefinitions,destructuring,andmodules.SinceAngular2takesadvantageoftheES2016decoratorsandmoreaccuratelytheirextensioninTypeScript,asectionwasdedicatedtothem.
Afterthis,wetookalookathowwecantakeadvantageofstatictypingbyusingexplicittypedefinitions.Wedescribedsomeofthebuilt-intypesinTypeScriptandhowwecandefineclassesinthelanguagebyspecifyingaccessmodifiersfortheirmembers.Ournextstopwastheinterfaces.WeendedouradventuresinTypeScriptbyexplainingthetypeparametersandtheambienttypedefinitions.
Inthenextchapter,wearegoingtostartexploringAngular2indepthbyusingtheframework’scomponentsanddirectives.
Chapter4.GettingStartedwithAngular2ComponentsandDirectivesBythispoint,you’realreadyfamiliarwiththecorebuildingblocksthatAngular2providesforthedevelopmentofsingle-pageapplicationsandtherelationsbetweenthem.However,we’vetouchedonlythesurfacebyintroducingthegeneralideabehindAngular’sconceptsandthebasicsyntaxusedfortheirdefinition.Inthischapter,we’lltakeadeepdiveintoAngular2’scomponentsanddirectives.
Inthefollowingsections,wewillcoverthesetopics:
EnforcedseparationofconcernsofthebuildingblocksthatAngular2providesfordevelopingapplications.TheappropriateuseofdirectivesorcomponentswheninteractingwiththeDOM.Built-indirectivesanddevelopingcustomones.Anin-depthlookatcomponentsandtheirtemplates.Contentprojection.Viewchildrenversuscontentchildren.Thecomponent’slifecycle.Usingtemplatereferences.ConfiguringAngular’schangedetection.
TheHelloworld!applicationinAngular2Now,let’sbuildourfirst“Helloworld!”appinAngular2!Inordertogeteverythingupandrunningaseasyandquicklyaspossible,forourfirstapplication,wewillusetheECMAScript5syntaxwiththetranspiledbundleofAngular2.First,createtheindex.htmlfilewiththefollowingcontent:
<!--ch4/es5/hello-world/index.html-->
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="UTF-8">
<title></title>
</head>
<body>
<scriptsrc="https://code.angularjs.org/2.0.0-beta.9/angular2-
polyfills.min.js"></script>
<scriptsrc="https://code.angularjs.org/2.0.0-beta.9/Rx.umd.min.js">
</script>
<scriptsrc="https://code.angularjs.org/2.0.0-beta.9/angular2-
all.umd.min.js"></script>
<scriptsrc="./app.js"></script>
</body>
</html>
TheprecedingHTMLfiledefinesthebasicstructureofourpage.Justbeforeclosingthebodytag,wehavereferencestofourscriptfiles:thepolyfillsrequiredbytheframework(includingES2015shim,zone.js,andothers),RxJS,theES5bundleofAngular2,andthefilethatcontainstheapplicationwe’regoingtobuild.
NoteRxJSisusedbyAngular’scoreinordertoallowustoempowerthereactiveprogrammingparadigminourapplications.Inthefollowingcontent,wewilltakeonlyashallowlookathowwecantakeadvantageofobservables.Forfurtherinformation,youcanvisittheRxJSGitHubrepositoryathttps://github.com/Reactive-Extensions/RxJS.
Inthesamedirectorywhereyourindex.htmlresides,createafilecalledapp.jsandenterthefollowingcontentinsideit:
//ch4/es5/hello-world/app.js
varApp=ng.core.Component({
selector:'app',
template:'<h1>Hello{{target}}!</h1>'
})
.Class({
constructor:function(){
this.target='world';
}
});
ng.platform.browser.bootstrap(App);
Intheprecedingsnippet,wedefineacomponentcalledAppwithanappselector.Thisselectorwillmatchalltheappelementsinsideourtemplatesthatareinthescopeoftheapplication.Thecomponenthasthefollowingtemplate:
'<h1>Hello{{target}}!</h1>'
ThissyntaxshouldalreadybefamiliartoyoufromAngularJS1.x.Whencompiledinthecontextofthegivencomponent,theprecedingsnippetwillinterpolatethetemplatewiththeresultoftheexpressioninsidethecurlybrackets.Inourcase,theexpressionissimplythetargetvariable.
ToClass,wepassanobjectliteral,whichhasasinglemethodcalledconstructor.ThisDSLprovidesanalternativewaytodefineclassesinECMAScript5.Inthebodyoftheconstructorfunction,weaddapropertycalledtargetwithavalueofthe"world"string.Inthelastlineofthesnippet,weinvokethebootstrapmethodinordertoinitializeourapplicationwithAppasarootcomponent.
Notethatbootstrapislocatedunderng.platform.browser.Thisisduetothefactthattheframeworkisbuiltfordifferentplatformsinmind,suchasabrowser,NativeScript,andsoon.Byplacingthebootstrapmethodsusedbythedifferentplatformsunderaseparatenamespace,Angular2canimplementdifferentlogictoinitializetheapplicationandalsoincludedifferentsetsofprovidersanddirectivesthatareplatformspecific.
Now,ifyouopenindex.htmlwithyourbrowser,youshouldseesomeerrors,asshowninthefollowingscreenshot:
Thishappenedbecausewemissedsomethingquiteimportant.Wedidn’tusetherootcomponentanywhereinsideindex.html.Inordertofinishtheapplication,addthefollowingHTMLelementaftertheopentagofthebodyelement:
<app></app>
Now,youcanrefreshyourbrowsertoseethefollowingresult:
NoteUsingTypeScript
AlthoughwealreadyhaveanAngular2applicationrunning,wecandomuchbetter!Wedidn’tuseanypackagemanagerormoduleloader.WespentallofChapter3,TypeScriptCrashCourse,talkingaboutTypeScript;however,wedidn’twriteasinglelineofitintheprecedingapplication.AlthoughitisnotrequiredthatyouuseTypeScriptwithAngular2,it’smuchconvenienttotakeadvantageofallthebonusesthatstatictypingprovides.
SettingupourenvironmentThecoreteamofAngulardevelopedabrandnewCLItoolforAngular2,whichallowsustobootstrapourapplicationswithafewcommands.Althoughwearegoingtointroduceitinthelastchapter,bythen,inordertoboostourlearningexperience,wearegoingtousethecodelocatedathttps://github.com/mgechev/switching-to-angular2.ItincludesalltheexamplesinthisbookandallowsustoquicklybootstrapourAngular2application(youcanknowmoreonhowtoquicklystartdevelopingwebapplicationswithAngular2inChapter5,DependencyInjectioninAngular2.).Ithasalltherequireddependenciesdeclaredinpackage.json,thedefinitionofbasicgulptasks,suchasthedevelopmentserver,thetranspilationofyourTypeScriptcodetoECMAScript5,live-reload,andsoon.Ourupcomingexamplesaregoingtobebasedonit.
Inordertosetuptheswitching-to-angular2project,you’llneedGit,Node.jsv5.x.x,andnpmupandrunningonyourcomputer.IfyouhaveadifferentversionoftheNode.jsinstalled,Irecommendthatyoutakealookatnvm(theNode.jsversionmanager,whichisavailableathttps://www.npmjs.com/package/nvm)orn(https://www.npmjs.com/package/n).Usingthesetools,you’llbeabletohavemultipleversionsofNode.jsonyourmachineandswitchbetweenthemwithasinglecommandviathecommandline.
InstallingourprojectrepositoryLet’sstartbysettinguptheswitching-to-angular2project.Openyourterminalandenterthefollowingcommands:
#Willclonetherepositoryandsaveittodirectorycalled
#switching-to-angular2
gitclonehttps://github.com/mgechev/switching-to-angular2.git
cdswitching-to-angular2
npminstall
Thefirstlinewillclonetheswitching-to-angular2projectintoadirectorycalledswitching-to-angular2.
Thelaststepbeforebeingabletoruntheseedprojectistoinstallalltherequireddependenciesusingnpm.ThisstepmaytakeawhiledependingonyourInternetconnection,sobepatientanddonotinterruptit.Ifyouencounteranyproblems,donothesitatetoraisetheissuesathttps://github.com/mgechev/switching-to-angular2/issues.
Thelaststepleftistostartthedevelopmentserver:
npmstart
Whentheprocessofthetranspilationiscompleted,yourbrowserwillautomaticallyopenwiththisURL:http://localhost:5555/dist/dev.Youshouldnowseeaviewsimilartowhatisshowninthefollowingscreenshot:
PlayingwithAngular2andTypeScriptNow,let’splayaroundwiththefileswealreadyhave!Navigatetotheapp/ch4/ts/hello-worlddirectoryinsideswitching-to-angular2.Then,openapp.tsandreplaceitscontentwiththefollowingsnippet:
//ch4/ts/hello-world/app.ts
import{Component}from'angular2/core';
import{bootstrap}from'angular2/platform/browser';
@Component({
selector:'app',
templateUrl:'./app.html'
})
classApp{
target:string;
constructor(){
this.target='world';
}
}
bootstrap(App);
Let’stakealookatthecodelinebyline:
import{Component}from'angular2/core';
import{bootstrap}from'angular2/platform/browser';
Initially,weimportthe@Componentdecoratorfromtheangular2/coremoduleandthebootstrapfunctionfromangular2/platform/browser.Later,[email protected]@Componentdecorator,wepassalmostthesameobjectliteralthatweusedintheECMAScript5versionoftheapplication,andthisway,wedefinetheCSSselectorforthecomponent.
Asanextstep,wedefinetheviewofthecomponent.However,notethatinthiscase,weusetemplateUrlinsteadofsimplyinliningthecomponent’stemplate.
Openapp.htmlandreplacethefile’scontentwith<h1>Hello{{target}}!</h1>.Thecontentofapp.htmlshouldbethesameastheinlinedtemplateweusedpreviously.Sincewecanuseatemplatebybothinliningit(withtemplate)andsettingitsURL(templateUrl),thecomponent’sAPIisquitesimilartotheAngularJS1.xdirectivesAPI.
Inthelastlineofthesnippet,webootstraptheapplicationbyprovidingtherootcomponent.
DiggingintotheindexNow,let’stakealookatindex.htmlinordertogetasenseofwhatgoesonwhenwestarttheapplication:
<!--ch4/ts/hello-world/index.html-->
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<metahttp-equiv="X-UA-Compatible"content="IE=edge">
<title><%=TITLE%></title>
<metaname="description"content="">
<metaname="viewport"content="width=device-width,initial-scale=1">
<!--inject:css-->
<!--endinject-->
</head>
<body>
<app>Loading…</app>
<!--inject:js-->
<!--endinject-->
<%=INIT%>
</body>
</html>
Notethatinsidethebodyofthepage,weusetheappelementwiththecontentofthetextnode,"Loading…",inside.The"Loading…"labelwillbevisibleuntiltheapplicationgetsbootstrappedandthemaincomponentgetsrendered.
NoteTherearetemplateplaceholders<%=INIT%>and<--inject:js…thatinjectcontentthatisspecifictoindividualdemos.TheyarenotAngularspecificbutinsteadaimtopreventcodeduplicationsinthecodesamplesattachedtothebookbecauseofthesharedstructurebetweenthem.InordertoseehowthisspecificHTMLfilehasbeentransformed,open/dist/dev/ch4/ts/hello-world/index.html.
UsingAngular2directivesWealreadybuiltoursimple“Helloworld!”app.Now,let’sstartbuildingsomethingthatisclosertoareal-lifeapplication.Bytheendofthissection,we’llhaveasimpleapplicationthatlistsanumberofitemsweneedtodoandgreetsusattheheaderofthepage.
Let’sstartbydevelopingourappcomponent.Thetwomodificationsfromthepreviousexamplethatweneedtomakearetorenamethetargetpropertytonameandaddalistoftodostothecomponent’scontrollerdefinition:
//ch4/ts/ng-for/detailed-syntax/app.ts
import{Component}from'angular2/core';
import{bootstrap}from'angular2/platform/browser';
@Component({
selector:'app',
templateUrl:'./app.html',
})
classApp{
todos:string[];
name:string;
constructor(){
this.name='John';
this.todos=['Buymilk','Savetheworld'];
}
}
bootstrap(App);
Theonlythingleftistochangethetemplateinordertoconsumetheprovideddata.We’realreadyfamiliarwiththeng-repeatdirectivefromAngularJS1.x.Itallowsustoloopalistofitemsusingamicrosyntax,whichislaterinterpretedbyAngularJS1.x.However,thedirectivedoesn’tcarryenoughsemantics,soitishardtobuildtoolsthatperformstaticcodeanalysisandhelpusimproveourdevelopmentexperience.Sincetheng-repeatdirectiveisquiteuseful,Angular2tooktheideaandimproveditfurtherinordertoallowmoresophisticatedtoolingbyintroducingfurthersemanticsontopofit.ItallowsbetterstaticcodeanalysistobeperformedbyIDEsandtexteditors.Suchsupportwillpreventusfrommakingtyposinthecodewewriteandallowustohavesmootherdevelopmentexperience.
Inapp.html,addthefollowingcontent:
<!--ch4/ts/ng-for/detailed-syntax/app.html-->
<h1>Hello{{name}}!</h1>
<p>
Here'salistofthethingsyouneedtodo:
</p>
<ul>
<templatengForvar-todo[ngForOf]="todos">
<li>{{todo}}</li>
</template>
</ul>
NoteThetemplateelementisaplacewherewecanholdmarkupandmakesurethatitwon’tberenderedbythebrowser.Thisisquiteusefulifweneedtoembedthetemplatesofourapplicationdirectlyintothemarkupofthepageandletthetemplateenginewe’reusingtoprocessthemlater.Inthecurrentexample,thismeansthatiftheAngular2DOMcompilerdoesn’tprocesstheDOMtree,allwe’regoingtoseeonthescreenaretheh1,pelementsandtheulelementwithoutanylistitems.
Now,afteryourefreshyourbrowser,youshouldseethefollowingresult:
Sofar,sogood!Theonlynewthingsleftintheprecedingsnippetsaretheattributesofthetemplateelementthatwe’renotfamiliarwith,suchasngFor,var-todo,and[ngForOf].Let’stakealookatthem.
ThengFordirectiveThengFordirectiveisadirectivethatallowsustoloopoveracollectionofitemsanddoesexactlywhatng-repeatdoesinAngularJS1.x,butitbringssomeextrasemantics.NotethatthengForOfattributeissurroundedbybrackets.Atfirst,thesebracketsmightseemlikeinvalidHTML.However,accordingtotheHTMLspecification,theiruseispermittedinattributenames.TheonlythingtheW3Cvalidatorisgoingtocomplainaboutisthefactthatthetemplateelementdoesn’townsuchattributes;however,browserswon’thaveproblemsprocessingthemarkup.
Thesemanticsbehindthesebracketsisthatthevalueoftheattributesurroundedbythemisanexpression,whichneedstobeevaluated.
ImprovedsemanticsofthedirectivessyntaxInChapter1,GettingStartedwithAngular2,wementionedtheopportunityforimprovedtoolinginAngular2.AbigissueinAngularJS1.xisthedifferentwaysinwhichwecanusedirectives.Thisrequiresanunderstandingoftheattributevalues,whichcanbeliterals,expressions,callbacks,oramicrosyntax.Angular2eliminatesthisproblembyintroducingafewsimpleconventionsthatarebuiltintotheframework:
propertyName="value"
[propertyName]="expression"
(eventName)="handler()"
Inthefirstline,thepropertyNameattributeacceptsastringliteralasavalue.Angularwillnotprocesstheattribute’svalueanyfurther;itwilluseitthewayitissetinthetemplate.
Thesecondsyntax,[propertyName]="expression",givesahinttoAngular2thatthevalueoftheattributesshouldbehandledasanexpression.WhenAngular2findsanattributesurroundedbybrackets,itwillinterprettheexpressioninthecontextofthecomponentassociatedtothetemplate.Inshort,ifwewanttosetanon-stringvalueorresultofanexpressionasvalueofgivenpropertyweneedtousethissyntax.
Thelastexampleshowshowwecanbindtoevents.Thesemanticsbehind(eventName)="handler()"isthatwewanttohandlealleventscalledeventNamethataretriggeredbythegivencomponentwiththehandler()expression.
We’regoingtodiscussmoreexampleslaterinthischapter.
NoteAngularprovidesalternativecanonicalsyntax,whichallowsustodefinethebindingsoftheelementswithoutusingbrackets.Forinstance,thepropertybindingcanbeexpressedusingthefollowingcode:
<input[value]="foo">
Itcanalsobeexpressedusingthis:
<inputbind-value="foo">
Similarly,wecanexpresstheeventbindingswiththefollowingcode:
<button(click)="handle()">Clickme</button>
Theycanalsobeexpressedusingthis:
<buttonon-click="handle()">Clickme</button>
DeclaringvariablesinsideatemplateThelastthingleftfromtheprecedingtemplateisthevar-todoattribute.WhatwearetellingAngularusingthissyntaxisthatwewanttodeclareanewvariablecalledtodoandbindittotheindividualitemsfromthecollectionwegetfromtheevaluationoftheexpressionsetasavalueof[ngForOf].
UsingsyntaxsugarintemplatesAlthoughthetemplatesyntaxisawesomeandprovidesmuchmoremeaningofthecodetotheIDEsortexteditorsweuse,itisquiteverbose.Angular2providesanalternativesyntax,whichwillbedesugaredtotheoneshowninthepreceding.Insteadofusingvar-todo,forexample,wecanuse#todo,whichhasthesamesemantics.
ThereareafewAngular2directivesthatrequiretheusageofatemplateelement,forexample,ngForOf,ngIf,andngSwitch.Sincesuchdirectivesareusedoften,there’sanalternativesyntaxforthem.Insteadoftypingdowntheentiretemplateelementexplicitly,wecansimplyprefixthedirectivewith*.ThiswillallowustoturnourngForOfdirectivesyntaxusageintothefollowing:
<!--ch4/ts/ng-for/syntax-sugar/app.html-->
<ul>
<li*ngFor="#todooftodos">{{todo}}</li>
</ul>
Later,thistemplatewillbedesugaredbyAngular2tothemoreverbosesyntaxdescribedearlier.Sincethelessverbosesyntaxiseasiertoreadandwrite,itsuseisconsideredasbestpractice.
NoteThe*characterallowsyoutoremovethetemplateelementandputthedirectivedirectlyontherootofthetemplateelement(intheprecedingexample,thelistitem,li).
DefiningAngular2directivesNowthatwe’vebuiltasimpleAngular2component,let’scontinueourjourneybyunderstandingtheAngular2directives.
UsingAngular2directives,wecanapplydifferentbehavioralorstructuralchangesovertheDOM.Inthisexample,we’regoingtobuildasimpletooltipdirective.
Incontrasttocomponents,directivesdonothaveviewsandtemplates,respectively.AnothercoredifferencebetweenthesetwoconceptsisthatthegivenHTMLelementmayhaveonlyasinglecomponentbutmultipledirectivesonit.Inotherwords,directivesaugmenttheelementscomparedtocomponentsthataretheactualelementsinourviews.
Angular’scoreteam’srecommendationistousedirectivesasattributes,prefixedwithanamespace.Keepingthisinmind,wewillusethetooltipdirectiveinthefollowingway:
<divsaTooltip="Helloworld!"></div>
Intheprecedingsnippet,weusethetooltipdirectiveoverthedivelement.Asanamespace,itsselectorusesthesastring.
NoteForsimplicity,intherestofthebookwemaynotprefixalltheselectorsofourcomponentsanddirectives.However,forproductionapplicationsfollowingbestpracticesisessential.YoucanfindanAngular2styleguidewhichpointsoutsuchpracticesathttps://github.com/mgechev/angular2-style-guide.
Beforeimplementingourtooltip,weneedtoimportacoupleofthingsfromangular2/core.OpenanewTypeScriptfilecalledapp.tsandenterthefollowingcontent;we’llfilltheplaceholderslater:
import{Directive,ElementRef,HostListener…}from'angular2/core';
Intheprecedingline,weimportthefollowingdefinitions:
ElementRef:Thisallowsustoinjecttheelementreference(we’renotlimitedtotheDOMonly)tothehostelement.Inthesampleusageoftheprecedingtooltip,wegetanAngularwrapperofthedivelement,whichholdsthetooltipattribute.Directive:Thisdecoratorallowsustoaddthemetadatarequiredforthenewdirectiveswedefine.HostListener(eventname):Thisisamethoddecoratorthatacceptsaneventnameasanargument.Duringinitializationofthedirective,Angular2willaddthedecoratedmethodasaneventhandlerfortheeventnameeventofthehostelement.
Let’slookatourimplementation;thisiswhatthedirective’sdefinitionlookslike:
//ch4/ts/tooltip/app.ts
@Directive({
selector:'[saTooltip]'
})
exportclassTooltip{
@Input()
saTooltip:string;
constructor(privateel:ElementRef,privateoverlay:Overlay){
this.overlay.attach(el.nativeElement);
}
@HostListener('mouseenter')
onMouseEnter(){
this.overlay.open(this.el,this.saTooltip);
}
@HostListener('mouseleave')
onMouseLeave(){
this.overlay.close();
}
}
Settingthedirective’sinputsIntheprecedingexample,wedeclareadirectivewiththesaTooltipselector.NotethatAngular’sHTMLcompileriscasesensitive,whichmeansthatitwilldistinguishthe[satooltip]and[saTooltip]selectors.Later,wewilldeclaretheinputofthedirectiveusingthe@InputdecoratoroverthesaTooltipproperty.Thesemanticsbehindthiscodeis:declareapropertycalledsaTooltipandbindittothevalueoftheresultthatwegotfromtheevaluationoftheexpressionpassedtothesaTooltipattribute.
The@Inputdecoratoracceptsasingleargument—thenameoftheattributewewanttobindto.Incasewedon’tpassanargument,Angularwillcreateabindingbetweentheattributewiththesamenameasthepropertyitself.Wewillexplaintheconceptofinputandoutputindetaillaterinthischapter.
Understandingthedirective’sconstructorTheconstructordeclarestwoprivateproperties:eloftheElementReftypeandoverlayoftheOverlaytype.TheOverlayclassimplementslogictomanagethetooltips’overlaysandisgoingtobeinjectedusingtheDImechanismofAngular.Inordertodeclareitasavailableforinjection,weneedtodeclarethetop-levelcomponentinthefollowingway:
@Component({
selector:'app',
templateUrl:'./app.html',
providers:[Overlay],
//...
})
classApp{}
NoteWe’regoingtotakealookatthedependencyinjectionmechanismofAngular2inthenextchapter,wherewewillexplainthewayinwhichwecandeclarethedependenciesofourservices,directives,andcomponents.
TheimplementationoftheOverlayclassisnotimportantforthepurposeofthischapter.However,ifyou’reinterestedinit,youcanfindtheimplementationin:ch4/ts/tooltip/app.ts.
BetterencapsulationofdirectivesInordertomakethetooltipdirectiveavailabletotheAngular’scompiler,weneedtoexplicitlydeclarewhereweintendtouseit.Forinstance,takealookattheAppclassatch4/ts/tooltip/app.ts;there,youcannoticethefollowing:
@Component({
selector:'app',
templateUrl:'./app.html',
providers:[Overlay],
directives:[Tooltip]
})
classApp{}
Tothe@Componentdecorator,wepassanobjectliteralthathasthedirectivesproperty.Thispropertycontainsalistofallthedirectivesthatshouldbeavailableintheentirecomponentsubtreewiththerootofthegivencomponent.
Atfirst,itmightseemannoyingthatyoushouldexplicitlydeclareallthedirectivesthatyourcomponentuses;however,thisenforcesbetterencapsulation.InAngularJS1.x,alldirectivesareinaglobalnamespace.Thismeansthatallthedirectivesdefinedintheapplicationareaccessibleinallthetemplates.Thisbringsinsomeproblems,forexample,namecollision.Inordertodealwiththisissue,we’veintroducednamingconventions,forinstance,the“ng-”prefixofallthedirectivesdefinedbyAngularJS1.xand“ui-”foralldirectivescomingwiththeAngularUI.
Thisway,byexplicitlydeclaringallthedirectives,thegivencomponentusesinAngular2,wecreateanamespacespecifictotheindividualcomponents’subtrees(thatis,thedirectiveswillbevisibletothegivenrootcomponentandallofitssuccessorcomponents).Preventingnamecollisionsisnottheonlybenefitweget;italsohelpsuswithbettersemanticsofthecodethatweproduce,sincewe’realwaysawareofthedirectivesaccessiblebythegivencomponent.Wecanfindalltheaccessibledirectivesofthegivencomponentbyfollowingthepathfromthecomponenttothetopofthecomponenttreeandtakingtheunionofallthevaluesofdirectivesarrayssetinthe@Componentdecorators.Giventhatcomponentsareextendedfromdirectives,weneedtoexplicitlydeclarealltheusedcomponentsaswell.
SinceAngular2definesasetofbuilt-indirectives,thebootstrapmethodpassestheminasimilarwayinordertomakethemavailableintheentireapplicationinordertopreventusfromcodeduplications.ThislistofpredefineddirectivesincludesNgClass,NgFor,NgIf,NgStyle,NgSwitch,NgSwitchWhen,andNgSwitchDefault.Theirnamesarequiteself-explanatory;we’lltakealookathowwecanusesomeofthemlaterinthischapter.
UsingAngular2’sbuilt-indirectivesNow,let’sbuildasimpleto-doapplicationinordertodemonstratethesyntaxtodefinecomponentsfurther!
Ourto-doitemswillhavethefollowingformat:
interfaceTodo{
completed:boolean;
label:string;
}
Let’sstartbyimportingeverythingwearegoingtoneed:
import{Component,ViewEncapsulation}from'angular2/core';
import{bootstrap}from'angular2/platform/browser';
Now,let’sdeclarethecomponentandthemetadataassociatedwithit:
@Component({
selector:'todo-app',
templateUrl:'./app.html',
styles:[
`ulli{
list-style:none;
}
.completed{
text-decoration:line-through;
}`
],
encapsulation:ViewEncapsulation.Emulated
})
Here,wespecifythattheselectoroftheTodocomponentwillbethetodo-appelement.Later,weaddthetemplateURL,whichpointstotheapp.htmlfile.Afterthat,weusethestylesproperty;thisisthefirsttimeweencounterit.Aswecanguessfromitsname,itisusedtosetthestylesofthecomponent.
Introducingthecomponent’sviewencapsulationAsweknow,Angular2isinspiredfromWebComponents,whosecorefeatureistheshadowDOM.TheshadowDOMallowsustoencapsulatethestylesofourWebComponentswithoutallowingthemtoleakoutsidethecomponent’sscope.Angular2providesthisfeature.IfwewantAngular’srenderertousetheshadowDOM,wecanuseViewEncapsulation.Native.However,theshadowDOMisnotsupportedbyallbrowsers;ifwewanttohavethesamelevelofencapsulationwithoutusingtheshadowDOM,wecanuseViewEncapsulation.Emulated.Ifwedon’twanttohaveanyencapsulationatall,wecanuseViewEncapsulation.None.Bydefault,therendererusesencapsulationofthetypeEmulated.
Implementingthecomponent’scontrollersNow,let’scontinuewiththeimplementationoftheapplication:
//ch4/ts/todo-app/app.ts
classTodoCtrl{
todos:Todo[]=[{
label:'Buymilk',
completed:false
},{
label:'Savetheworld',
completed:false
}];
name:string='John';
addTodo(label){…}
removeTodo(idx){…}
toggleCompletion(idx){…}
}
HereispartoftheimplementationofthecontrollerassociatedwiththetemplateoftheTodoapplication.
Insidetheclassdeclaration,weinitializedthetodospropertytoanarraywithtwotodoitems:
{
label:'Buymilk',
completed:false
},{
label:'Savetheworld',
completed:false
}
Now,let’supdatethetemplateandrendertheseitems!Here’showthisisdone:
<ul>
<li*ngFor="#todooftodos;varindex=index"
[class.completed]="todo.completed">
<inputtype="checkbox"[checked]="todo.completed"
(change)="toggleCompletion(index)">
{{todo.label}}
</li>
</ul>
Intheprecedingtemplate,weloopedallthetodoitemsinsidethetodospropertyofthecontroller.Foreachtodoitem,wecreatedacheckboxthatcantoggletheitem’scompletionstatus;wealsorenderedthetodoitem’slabelwiththeinterpolationdirective.Here,wecannoticethesyntaxthatwasexplainedearlier:
Webindtothechangeeventofthecheckboxusing(change)="statement".Webindtothepropertyofthetodoitemusing[checked]="expr".
Inordertohavealineacrossthecompletedtodoitems,webindtotheclass.completedpropertyoftheelement.Sincewewanttoapplythecompletedclasstoallthecompleted
to-doitems,weuse[class.completed]="todo.completed".Thisway,wedeclarethatwewanttoapplythecompletedclassdependingonthevalueofthetodo.completedexpression.Hereishowourapplicationlooksnow:
NoteSimilartotheclassbindingsyntax,Angularallowsustobindtotheelement’sstylesandattributes.Forinstance,wecanbindtothetdelement’scolspanattributeusingthefollowinglineofcode:
<td[attr.colspan]="colspanCount"></td>
Inthesameway,wecanbindtoanystylepropertyusingthislineofcode:
<div[style.backgroundImage]="expression"></td>
HandlinguseractionsSofar,sogood!Now,let’simplementthetoggleCompletionmethod.Thismethodacceptstheindexoftheto-doitemasasingleargument:
toggleCompletion(idx){
lettodo=this.todos[idx];
todo.completed=!todo.completed;
}
IntoggleCompletion,wesimplytogglethecompletedBooleanvalueassociatedwiththecurrentto-doitem,whichisspecifiedbytheindexpassedasanargumenttothemethod.
Now,let’saddatextinputtoaddthenewto-doitems:
<p>
Addanewtodo:
<input#newtodotype="text">
<button(click)="addTodo(newtodo.value);newtodo.value=''">
Add
</button>
</p>
Theinputheredefinesanewidentifiercallednewtodo.Wecanreferencetheinputusingthenewtodoidentifierinsidethetemplate.Oncetheuserclicksonthebutton,theaddTodomethoddefinedinthecontrollerwillbeinvokedwiththevalueofthenewtodoinputasanargument.Insidethestatementthatispassedtothe(click)attribute,wealsoresetthevalueofthenewtodoinputbysettingittotheemptystring.
NoteNotethatdirectlymanipulatingDOMelementsisnotconsideredasbestpracticesinceitwillpreventourcomponentfromrunningproperlyoutsidethebrowserenvironment.WewillexplainhowwecanmigratethisapplicationtoWebWorkersinChapter8,DevelopmentExperienceandServer-SideRendering.
Now,let’sdefinetheaddTodomethod:
addTodo(label){
this.todos.push({
label,
completed:false
});
}
Insideit,wecreateanewto-doitemusingtheobjectliteralsyntax.
Theonlythingleftoutofourapplicationistoimplementremovalofexistingto-doitems.Sinceitisquitesimilartothefunctionalityusedtotogglethecompletionoftheto-doitems,I’llleaveitsimplementationasasimpleexerciseforthereader.
Usingadirectives’inputsandoutputsByrefactoringourtodoapplication,wearegoingtodemonstratehowwecantakeadvantageofthedirectives’inputsandoutputs:
Wecanthinkoftheinputsasproperties(orevenarguments)thatthegivendirectiveaccepts.Theoutputscouldbeconsideredaseventsthatittriggers.Whenweuseadirectiveprovidedbyathird-partylibrary,mostlywecareaboutisitsinputsandoutputsbecausetheydefineitsAPI.
Inputsreferstovaluesthatparameterizethedirective’sbehaviorand/orview.Ontheotherhand,outputsreferstoeventsthatthedirectivefireswhensomethingspecialhappens.
Findingoutdirectives’inputsandoutputsNow,let’sdivideourmonolithicto-doapplicationintoseparatecomponentsthatcommunicatewitheachother.Inthefollowingscreenshot,youcanseetheindividualcomponentsthatwhencomposedtogetherimplementthefunctionalityoftheapplication:
TheouterrectanglerepresentstheentireTodoapplication.Thefirstnestedrectanglecontainsthecomponentthatisresponsibleforenteringlabelsofthenewto-doitems,andtheonebelowitliststheindividualitemsthatarestoredintherootcomponent.
Havingsaidthis,wecandefinethesethreecomponentsasfollows:
TodoApp:Responsibleformaintainingthelistofto-doitems(addingnewitemsandtogglingthecompletionstatus).InputBox:Responsibleforenteringthelabelofthenewto-doitem.Ithasthefollowinginputsandoutputs:
Input:Aplaceholderforthetextboxandalabelforthesubmitbutton.Output:Itshouldemitthecontentoftheinputoncethesubmitbuttonisclicked.
TodoList:Thisisresponsibleforrenderingtheindividualto-doitems.Ithasthefollowinginputsandoutputs:
Input:Thelistofto-doitems.Output:Oncethecompletionstatusofanyoftheto-doitemschanges,thecomponentshouldemitthechange.
Now,let’sbeginwiththeimplementation!
Definingthecomponent’sinputsandoutputsLet’suseabottom-upapproachandstartwiththeInputBoxcomponent.Beforethat,weneedacoupleofimportsfromAngular’sangular2/corepackage:
import{
Component,
Input,
Output,
EventEmitter
}from'angular2/core';
Intheprecedingcode,weimportedthe@Component,@Input,[email protected],@Inputand@Outputareusedfordeclaringthedirective’sinputsandoutputs.EventEmitterisagenericclass(thatis,acceptingtypeparameter)whichcombinedwiththe@Outputdecoratorhelpsusemitoutputs.
Asthenextstep,let’stakealookattheInputBoxcomponent’sdeclaration:
//ch4/ts/inputs-outputs/app.ts
@Component({
selector:'text-input',
template:`
<input#todoInput[placeholder]="inputPlaceholder">
<button(click)="emitText(todoInput.value);
todoInput.value='';">
{{buttonLabel}}
</button>
`
})
classInputBox{...}
Notethatinthetemplate,wedeclareatextinputcalledtodoInputandsetitsplaceholderpropertytothevaluethatwegotfromtheevaluationoftheinputPlaceholderexpression.ThevalueoftheexpressionisthevalueoftheinputPlaceholderpropertydefinedinthecomponent’scontroller.Thisisthefirstinputthatweneedtodefine:
classInputBox{
@Input()inputPlaceholder:string;
...
}
Similarly,wedeclaretheotherinputofthebuttonLabelcomponent,whichweuseasavalueofthelabelofthebutton:
classInputBox{
@Input()inputPlaceholder:string;
@Input()buttonLabel:string;
...
}
Intheprecedingtemplate,webindtheclickeventofthebuttontothisexpression:emitText(todoInput.value);todoInput.value='';.TheemitTextmethodis
supposedtobedefinedinthecomponent’scontroller;onceitisinvoked,itshouldemitthevalueofthetextinput.Hereishowwecanimplementthisbehavior:
classInputBox{
...
@Output()inputText=newEventEmitter<string>();
emitText(text:string){
this.inputText.emit(text);
}
}
Initially,wedeclareanoutputcalledinputText.Asitsvalue,wesetanewinstanceofthetypeEventEmitter<string>thatwecreate.
NoteNotethatalltheoutputsofallthecomponentsneedtobeinstancesofEventEmitter.
InsidetheemitTextmethod,weinvoketheemitmethodoftheinputTextinstancewiththeargumentofthevalueofthetextinput.
Now,let’sdefinetheTodoListcomponentinthesamefashion:
@Component(...)
classTodoList{
@Input()todos:Todo[];
@Output()toggle=newEventEmitter<Todo>();
toggleCompletion(index:number){
lettodo=this.todos[index];
this.toggle.emit(todo);
}
}
Sincethevalueoftheobjectliteralpassedtothe@Componentdecoratorisnotessentialforthepurposeofthissection,we’veomittedit.Thecompleteimplementationofthisexamplecouldbefoundatch4/ts/inputs-outputs/app.ts.Let’stakealookatthebodyoftheTodoListclass.Similarly,fortheInputBoxcomponent,wedefinethetodosinput.Wealsodefinethetoggleoutputbydeclaringthetoggleproperty,settingitsvaluetoanewinstanceofthetypeEventEmitter<Todo>anddecoratingitwiththe@Outputdecorator.
PassinginputsandconsumingtheoutputsNow,let’scombinethecomponentswedefinedintheprecedingsectionandimplementourcompleteapplication!
ThelastcomponentweneedtotakealookatisTodoApp:
@Component({
selector:'todo-app',
directives:[TodoList,InputBox],
template:`
<h1>Hello{{name}}!</h1>
<p>
Addanewtodo:
<input-boxinputPlaceholder="Newtodo…"
buttonLabel="Add"
(inputText)="addTodo($event)">
</input-box>
</p>
<p>Here'sthelistofpendingtodoitems:</p>
<todo-list[todos]="todos"(toggle)="toggleCompletion($event)"></todo-
list>
`
})
classTodoApp{...}
Initially,wedefinetheTodoAppclassanddecorateitwiththe@Componentdecorator.Notethatinthelistofthedirectivesusedbythecomponent,weincludeInputBoxandTodoList.Themagicofhowthesecomponentscollaboratetogetherhappensinthetemplate:
<input-boxinputPlaceholder="Newtodo…"
buttonLabel="Add"
(inputText)="addTodo($event)">
</input-box>
First,weusetheInputBoxcomponentandpassvaluestotheinputs:inputPlaceholderandbuttonLabel.Notethatjustlikewesawearlier,ifwewanttopassanexpressionasavaluetoanyoftheseinputs,weneedtosurroundthemwithbrackets(thatis,[inputPlaceholder]="expression").Inthiscase,theexpressionwillbeevaluatedinthecontextofthecomponentthatownsthetemplate,anditwillbepassedasaninputtothecomponentthatownsthegivenproperty.
RightafterwepassthevalueforthebuttonLabelinput,weconsumetheinputTextoutputbysettingthevalueofthe(inputText)attributetotheaddTodo($event)expression.Thevalueof$eventwillequalthevaluewepassedtotheemitmethodoftheinputTextobjectinsidetheemitTextmethodofInputBox(incasewebindtoanativeevent,thevalueoftheeventobjectwillbethenativeeventobjectitself).
Inthesameway,wepasstheinputoftheTodoListcomponentandhandleitstoggleoutput.Now,let’sdefinethelogicbehindtheTodoAppcomponent:
classTodoApp{
todos:Todo[]=[];
name:string='John';
addTodo(label:string){
this.todos.push({
label,
completed:false
});
}
toggleCompletion(todo:Todo){
todo.completed=!todo.completed;
}
}
IntheaddTodomethod,wesimplypushanewto-doitemtothetodosarray.TheimplementationoftoggleCompletionisevensimpler—wetogglethevalueofthecompletedflagthatispassedasanargumenttotheto-doitem.Now,wearefamiliarwiththebasicsofthecomponents’inputsandoutputs!
EventbubblingInAngular,wehavethesamebubblingbehaviorwehaveintheDOM.Forinstance,ifwehavethefollowingtemplate:
<input-boxinputPlaceholder="Newtodo…"
buttonLabel="Add"
(click)="handleClick($event)"
(inputText)="addTodo($event)">
</input-box>
Thedeclarationofinput-boxlookslikethis:
<input#todoInput[placeholder]="inputPlaceholder">
<button(click)="emitText(todoInput.value);
todoInput.value='';">
{{buttonLabel}}
</button>
Oncetheuserclicksonthebuttondefinedwithinthetemplateoftheinput-boxcomponent,thehandleClick($event)expressionwillbeevaluated.
Further,thetargetpropertyofthefirstargumentofhandleClickwillbethebuttonitself,butthecurrentTargetpropertywillbetheinput-boxelement.
NoteNotethatunlikenativeevents,onestriggeredbyEventEmitterwillnotbubble.
RenamingtheinputsandoutputsofadirectiveNow,wewillexplorehowwecanrenamethedirectives’inputsandoutputs!Let’ssupposethatwehavethefollowingdefinitionoftheTodoListcomponent:
classTodoList{
...
@Output()toggle=newEventEmitter<Todo>();
toggle(index:number){
...
}
}
Theoutputofthecomponentiscalledtoggle;themethodthathandleschangesinthecheckboxesresponsiblefortogglingcompletionoftheindividualto-doitemsiscalledtoggleaswell.ThiscodewillnotbecompiledasintheTodoListcontroller,wehavetwoidentifiersnamedinthesameway.Wehavetwooptionshere:wecaneitherrenamethemethodortheproperty.Ifwerenametheproperty,thiswillchangethenameofthecomponent’soutputaswell.So,thefollowinglineofcodewillnolongerwork:
<todo-list[toggle]="foobar($event)"...></todo-list>
Whatwecandoinsteadisrenamethetogglepropertyandexplicitlysetthenameoftheoutputusingthe@Outputdecorator:
classTodoList{
...
@Output('toggle')toggleEvent=newEventEmitter<Todo>();
toggle(index:number){
...
}
}
Thisway,wewillbeabletotriggerthetoggleoutputusingthetoggleEventproperty.
NoteNotethatsuchrenamescouldbeconfusingandarenotconsideredasbestpractices.Foracompletesetofbestpracticesvisithttps://github.com/mgechev/angular2-style-guide.
Similarly,wecanrenamecomponent’sinputsusingthefollowingcodesnippet:
classTodoList{
@Input('todos')todoList:Todo[];
@Output('toggle')toggleEvent=newEventEmitter<Todo>();
toggle(index:number){
...
}
}
Now,nomatterthatwerenamedtheinputandoutputpropertiesofTodoList,itstillhasthesamepublicinterface:
<todo-list[todos]="todos"
(toggle)="toggleCompletion($event)">
</todo-list>
AnalternativesyntaxtodefineinputsandoutputsThe@Inputand@Outputdecoratorsaresyntaxsugarforeasierdeclarationofthedirective’sinputsandoutputs.Theoriginalsyntaxforthispurposeisasfollows:
@Directive({
outputs:['outputName:outputAlias'],
inputs:['inputName:inputAlias']
})
classDir{
outputName=newEventEmitter();
}
Using@Inputand@Output,theprecedingsyntaxisequivalenttothis:
@Directive(...)
classDir{
@Output('outputAlias')outputName=newEventEmitter();
@Input('inputAlias')inputName;
}
Althoughbothhavethesamesemantics,accordingtothebestpractices,weshouldusethelatteronebecauseitiseasiertoreadandunderstand.
ExplainingAngular2’scontentprojectionContentprojectionisanimportantconceptwhendevelopinguserinterfaces.Itallowsustoprojectpiecesofcontentintodifferentplacesoftheuserinterfaceofourapplication.WebComponentssolvethisproblemwiththecontentelement.InAngularJS1.x,itisimplementedwiththeinfamoustransclusion.
Angular2isinspiredbymodernwebstandards,especiallyWebComponents,whichledtotheadoptionofsomeofthemethodsofcontentprojectionusedthere.Inthissection,we’lllookattheminthecontextofAngular2usingtheng-contentdirective.
BasiccontentprojectioninAngular2Let’ssupposewe’rebuildingacomponentcalledfancy-button.ThiscomponentwillusethestandardHTMLbuttonelementandaddsomeextrabehaviortoit.Hereisthedefinitionofthefancy-buttoncomponent:
@Component({
selector:'fancy-button',
template:'<button>Clickme</button>'
})
classFancyButton{…}
Insideofthe@Componentdecorator,wesettheinlinetemplateofthecomponenttogetherwithitsselector.Now,wecanusethecomponentwiththefollowingmarkup:
<fancy-button></fancy-button>
Onthescreen,wearegoingtoseeastandardHTMLbuttonthathasalabelwiththecontentClickme.ThisisnotaveryflexiblewaytodefinereusableUIcomponents.Mostlikely,theusersofthefancybuttonwillneedtochangethecontentofthelabeltosomething,dependingontheirapplication.
InAngularJS1.x,wewereabletoachievethisresultwithng-transclude:
//AngularJS1.xexample
app.directive('fancyButton',function(){
return{
restrict:'E',
transclude:true,
template:'<button><ng-transclude></ng-transclude></button>'
};
});
InAngular2,wehavetheng-contentelement:
//ch4/ts/ng-content/app.ts
@Component({
selector:'fancy-button',
template:'<button><ng-content></ng-content></button>'
})
classFancyButton{/*Extrabehavior*/}
Now,wecanpasscustomcontenttothefancybuttonbyexecutingthis:
<fancy-button>Click<i>me</i>now!</fancy-button>
Asaresult,thecontentbetweentheopeningandtheclosingfancy-buttontagswillbeplacedwheretheng-contentdirectiveresides.
ProjectingmultiplecontentchunksAnothertypicalusecaseofcontentprojectioniswhenwepasscontenttoacustomAngular2componentorAngularJS1.xdirectiveandwewantdifferentpartsofthiscontenttobeprojectedtodifferentlocationsinthetemplate.
Forinstance,let’ssupposewehaveapanelcomponentthathasatitleandabody:
<panel>
<panel-title>Sampletitle</panel-title>
<panel-content>Content</panel-content>
</panel>
Andwehavethefollowingtemplateofourcomponent:
<divclass="panel">
<divclass="panel-title">
<!--Projectthecontentofpanel-titlehere-->
</div>
<divclass="panel-content">
<!--Projectthecontentofpanel-contenthere-->
</div>
</div>`
InAngularJS1.5,weareabletodothisusingmulti-slottransclusion,whichwasimplementedinordertoallowustohaveasmoothertransitiontoAngular2.Let’stakealookathowwecanproceedinAngular2inordertodefinesuchapanelcomponent:
//ch4/ts/ng-content/app.ts
@Component({
selector:'panel',
styles:[…],
template:`
<divclass="panel">
<divclass="panel-title">
<ng-contentselect="panel-title"></ng-content>
</div>
<divclass="panel-content">
<ng-contentselect="panel-content"></ng-content>
</div>
</div>`
})
classPanel{}
Wehavealreadydescribedtheselectorandstylesproperties,solet’stakealookatthecomponent’stemplate.Wehaveadivelementwiththepanelclass,whichwrapsthetwonesteddivelements,respectively:oneforthetitleofpanelandoneforthecontentofpanel.Inordertograbthecontentfromthepanel-titleelementandprojectitwherethetitleofthepanelissupposedtobeintherenderedpanel,weneedtousetheng-contentelementwiththeselectorattribute,whichhasthepanel-titlevalue.ThevalueoftheselectorattributeisaCSSselector,whichinthiscaseisgoingtomatchallthepanel-titleelementsthatresideinsidethetargetpanelelement.Afterthis,ng-contentwillgrabtheircontentandsetthemasitsowncontent.
NestingcomponentsWe’vealreadybuiltafewsimpleapplicationsasacompositionofcomponentsanddirectives.Wesawthatcomponentsarebasicallydirectiveswithviews,sowecanimplementthembynesting/composingotherdirectivesandcomponents.Thefollowingfigureillustratesthiswithastructuraldiagram:
Thecompositioncouldbeachievedbynestingdirectivesandcomponentswithinthecomponents’templates,takingadvantageofthenestednatureoftheusedmarkup.Forinstance,let’ssaywehaveacomponentwiththesample-componentselector,whichhasthefollowingdefinition:
@Component({
selector:'sample-component',
template:'<view-child></view-child>'
})
classSample{}
Thetemplateofthesample-componentselectorhasasinglechildelementwiththetagnameview-child.
Ontheotherhand,wecanusethesample-componentselectorinsidethetemplateofanothercomponent,andsinceitcanbeusedasanelement,wecannestothercomponentsordirectivesinsideit:
<sample-component>
<content-child1></content-child1>
<content-child2></content-child2>
</sample-component>
Thisway,thesample-componentcomponenthastwodifferenttypesofsuccessors:
Thesuccessordefinedwithinitstemplate.Thesuccessorthatispassedasnestedelementsbetweenitsopeningandclosingtags.
InthecontextofAngular2,thedirectchildrenelementsdefinedwithinthecomponent’stemplatearecalledviewchildrenandtheonesnestedbetweenitsopeningandclosingtagsarecalledcontentchildren.
UsingViewChildrenandContentChildrenLet’stakealookattheimplementationoftheTabscomponent,whichusesthefollowingstructure:
<tabs(changed)="tabChanged($event)">
<tab-title>Tab1</tab-title>
<tab-content>Content1</tab-content>
<tab-title>Tab2</tab-title>
<tab-content>Content2</tab-content>
</tabs>
Theprecedingstructureiscomposedofthreecomponents:
TheTabcomponent.TheTabTitlecomponent.TheTabContentcomponent.
Let’slookattheimplementationoftheTabTitlecomponent:
@Component({
selector:'tab-title',
styles:[…],
template:`
<divclass="tab-title"(click)="handleClick()">
<ng-content></ng-content>
</div>
`
})
classTabTitle{
tabSelected:EventEmitter<TabTitle>=
newEventEmitter<TabTitle>();
handleClick(){
this.tabSelected.emit(this);
}
}
There’snothingnewinthisimplementation.WedefineaTabTitlecomponent,whichhasasinglepropertycalledtabSelected.ItisofthetypeEventEmitterandwillbetriggeredoncetheuserclicksonthetabtitle.
Now,let’stakealookattheTabContentcomponent:
@Component({
selector:'tab-content',
styles:[…],
template:`
<divclass="tab-content"[hidden]="!isActive">
<ng-content></ng-content>
</div>
`
})
classTabContent{
isActive:boolean=false;
}
Thishasanevensimplerimplementation—allwedoisprojecttheDOMpassedtothetab-contentelementinsideng-contentandhideitoncethevalueoftheisActivepropertybecomesfalse.
TheinterestingpartoftheimplementationistheTabscomponentitself:
//ch4/ts/basic-tab-content-children/app.ts
@Component({
selector:'tabs',
styles:[…],
template:`
<divclass="tab">
<divclass="tab-nav">
<ng-contentselect="tab-title"></ng-content>
</div>
<ng-contentselect="tab-content"></ng-content>
</div>
`
})
classTabs{
@Output('changed')
tabChanged:EventEmitter<number>=newEventEmitter<number>();
@ContentChildren(TabTitle)
tabTitles:QueryList<TabTitle>;
@ContentChildren(TabContent)
tabContents:QueryList<TabContent>;
active:number;
select(index:number){…}
ngAfterViewInit(){…}
}
Inthisimplementation,wehaveadecoratorthatwehaven’tusedyet—[email protected]@ContentChildrenpropertydecoratorfetchesthecontentchildrenofthegivencomponent.ThismeansthatwecangetreferencestoallTabTitleandTabContentinstancesfromwithintheinstanceoftheTabscomponentandgetthemintheorderinwhichtheyaredeclaredinthemarkup.There’sanalternativedecoratorcalled@ViewChildren,whichfetchesalltheviewchildrenofthegivenelement.Let’stakealookatthedifferencebetweenthembeforeweexplaintheimplementationfurther.
ViewChildversusContentChildAlthoughbothconceptssoundsimilar,theyhavequitedifferentsemantics.Inordertounderstandthembetter,let’stakealookatthefollowingexample:
//ch4/ts/view-child-content-child/app.ts
@Component({
selector:'user-badge',
template:'…'
})
classUserBadge{}
@Component({
selector:'user-rating',
template:'…'
})
classUserRating{}
Here,we’vedefinedtwocomponents:UserBadgeandUserRating.Let’sdefineaparentcomponent,whichcomprisesboththecomponents:
@Component({
selector:'user-panel',
template:'<user-badge></user-badge>',
directives:[UserBadge]
})
classUserPanel{…}
NotethatthetemplateoftheviewofUserPanelcontainsonlytheUserBadgecomponent’sselector.Now,let’susetheUserPanelcomponentinourapplication:
@Component({
selector:'app',
template:`<user-panel>
<user-rating></user-rating>
</user-panel>`,
directives:[CORE_DIRECTIVES,UserPanel,UserRating]
})
classApp{
constructor(){}
}
ThetemplateofourmainAppcomponentusestheUserPanelcomponentandneststheUserRatingcomponentinsideit.Now,let’ssupposewewanttogetareferencetotheinstanceoftheUserRatingcomponentthatisusedinsidetheuser-panelelementintheAppcomponentandareferencetotheUserBadgecomponent,whichisusedinsidetheUserPaneltemplate.Inordertodothis,wecanaddtwomorepropertiestotheUserPanelcontrollerandaddthe@ContentChildand@ViewChilddecoratorstothemwiththeappropriatearguments:
classUserPanel{
@ViewChild(UserBadge)
badge:UserBadge;
@ContentChild(UserRating)
rating:UserRating;
constructor(){
//
}
}
Thesemanticsofthebadgepropertydeclarationisthis:“gettheinstanceofthefirstchildcomponentofthetypeUserBadge,whichisusedinsidetheUserPaneltemplate”.Accordingly,thesemanticsoftheratingproperty’sdeclarationisthis:“gettheinstanceofthefirstchildcomponentofthetypeUserRating,whichisnestedinsidetheUserPanelhostelement”.
Now,ifyourunthiscode,you’llnotethatthevaluesofthebadgeandratingpropertiesarestillequaltotheundefinedvalueinsidethecontroller’sconstructor.Thisisbecausetheyarestillnotinitializedinthisphaseofthecomponent’slifecycle.ThelifecyclehooksthatwecanuseinordertogetareferencetothesechildcomponentsarengAfterViewInitandngAfterContentInit.WecanusethesehookssimplybyaddingdefinitionsofthengAfterViewInitandngAfterContentInitmethodstothecomponent’scontroller.WewillmakeacompleteoverviewofthelifecyclehooksthatAngular2providesshortly.
Torecap,wecansaythatthecontentchildrenofthegivencomponentsarethechildelementsthatarenestedwithinthecomponent’shostelement.Incontrast,theviewchildrendirectivesofthegivencomponentaretheelementsusedwithinitstemplate.
NoteInordertogetplatformindependentreferencetoaDOMelement,again,wecanuse@[email protected],ifwehavethefollowingtemplate:<input#todo>wecangetareferencetotheinputbyusing:@ViewChild('todo').
Sincewearealreadyfamiliarwiththecoredifferencesbetweenviewchildrenandcontentchildrennow,wecancontinuewithourtabsimplementation.
Inthetabscomponent,insteadofusingthe@ContentChilddecorator,weuse@ContentChildren.Wedothisbecausewehavemultiplecontentchildrenandwewanttogetthemall:
@ContentChildren(TabTitle)
tabTitles:QueryList<TabTitle>;
@ContentChildren(TabContent)
tabContents:QueryList<TabContent>;
AnothermaindifferencewecannoticeisthatthetypesofthetabTitlesandtabContentspropertiesareQueryListwiththerespectivetypeparameterandnotthecomponent’stypeitself.WecanthinkoftheQueryListdatastructureasaJavaScriptarray—wecanapplythesamehigh-orderfunctions(map,filter,reduce,andsoon)overitandloopoveritselements;however,QueryListisalsoobservable,thatis,wecanobserveitforchanges.
AsthefinalstepofourTabsdefinition,let’stakeapeekattheimplementationofthengAfterContentInitandselectmethods:
ngAfterContentInit(){
this.tabTitles
.map(t=>t.tabSelected)
.forEach((t,i)=>{
t.subscribe(_=>{
this.select(i)
});
});
this.active=0;
this.select(0);
}
Inthefirstlineofthemethod’simplementation,weloopalltabTitlesandtaketheobservable’sreferences.Theseobjectshaveamethodcalledsubscribe,whichacceptsacallbackasanargument.Oncethe.emit()methodoftheEventEmitterinstance(thatis,thetabSelectedpropertyofanytab)iscalled,thecallbackpassedtothesubscribemethodwillbeinvoked.
Now,let’stakealookattheselectmethod’simplementation:
select(index:number){
letcontents:TabContent[]=this.tabContents.toArray();
contents[this.active].isActive=false;
this.active=index;
contents[this.active].isActive=true;
this.tabChanged.emit(index);
}
Inthefirstline,wegetanarrayrepresentationoftabContents,whichisofthetypeQueryList<TabContent>.Afterthat,wesettheisActiveflagofthecurrentactivetabtofalseandselectthenextactiveone.Inthelastlineintheselectmethod’simplementation,wetriggertheselectedeventoftheTabscomponentbyinvokingthis.tabChanged.emitwiththeindexofthecurrentlyselectedtab.
Hookingintothecomponent’slifecycleComponentsinAngular2haveawell-definedlifecycle,whichallowsustohookintodifferentphasesofitandhavefurthercontroloverourapplication.Wecandothisbyimplementingspecificmethodsinthecomponent’scontroller.Inordertobemoreexplicit,thankstotheexpressivenessofTypeScript,wecanimplementdifferentinterfacesassociatedwiththelifecycle’sphases.Eachoftheseinterfaceshasasinglemethod,whichisassociatedwiththephaseitself.
Althoughcodewrittenwithexplicitinterfaceimplementationwillhavebettersemantics,sinceAngular2supportsES5aswellwithinthecomponent,wecansimplydefinemethodswiththesamenamesasthelifecyclehooks(butthistime,prefixedwithng)andtakeadvantageofducktyping.
Thefollowingdiagramshowsallthephaseswecanhookinto:
Let’stakealookatthedifferentlifecyclehooks:
OnChanges:Thishookwillbeinvokedonceachangeintheinputpropertiesofagivencomponenthasbeendetected.Forinstance,let’stakealookatthefollowingcomponent:
@Component({
selector:'panel',
inputs:['title']
})
classPanel{…}
Wecanuseitlikethis:
<panel[title]="expression"></panel>
Oncethevalueoftheexpressionassociatedwiththe[title]attributehasbeenchanged,thengOnChangeshookwillbeinvoked.Wecanimplementitusingthiscodesnippet:
@Component(…)
classPanel{
ngOnChanges(changes){
Object.keys(changes).forEach(prop=>{
console.log(prop,'changed.Previousvalue',
changes[prop].previousValue);
});
}
}
Theprecedingsnippetwilldisplayallthechangedbindingsandtheiroldvalues.Inordertobemoreexplicitintheimplementationofthehook,wecanuseinterfaces:
import{Component,OnChanges}from'angular2/core';
@Component(…)
classPanelimplementsOnChanges{
ngOnChanges(changes){…}
}
Alltheinterfacesrepresentingtheindividuallifecyclehooksdefineasinglemethodwiththenameoftheinterfaceitselfprefixedwithng.Intheupcominglist,we’llusethetermlifecyclehook,bothforinterfaceand/orthemethod,exceptifwewon’timplyanythingspecificallyforonlyoneofthem.
OnInit:Thishookwillbeinvokedoncethegivencomponenthasbeeninitialized.WecanimplementitusingtheOnInitinterfacewithitsngOnInitmethod.DoCheck:Thiswillbeinvokedwhenthechangedetectorofthegivencomponentisinvoked.Itallowsustoimplementourownchangedetectionalgorithmforthegivencomponent.NotethatDoCheckandOnChangesshouldnotbeimplementedtogetheronthesamedirective.OnDestroy:IfweimplementtheOnDestroyinterfacewithitssinglengOnDestroymethod,wecanhookintothedestroylifecyclephaseofacomponent.Thismethodwillbeinvokedoncethecomponentisdetachedfromthecomponenttree.
Now,let’stakealookatthelifecyclehooksassociatedwiththecomponent’scontentandviewchildren:
AfterContentInit:IfweimplementthengAfterContentInitlifecyclehook,wewillbenotifiedwhenthecomponent’scontenthasbeenfullyinitialized.ThisisthephasewhenthepropertiesdecoratedwithContentChildorContentChildrenwillbeinitialized.AfterContentChecked:Byimplementingthishook,we’llgetnotifiedeachtimethecontentofthegivencomponenthasbeencheckedbythechangedetectionmechanismofAngular2.AfterViewInit:IfweimplementthengAfterViewInitlifecyclehook,wewillbenotifiedwhenthecomponent’sviewhasbeenfullyinitialized.ThisisthephasewhenthepropertiesdecoratedwithViewChildorViewChildrenwillbeinitialized.AfterViewChecked:ThisissimilartoAfterContentChecked.TheAfterViewCheckedhookwillbeinvokedoncetheviewofyourcomponenthasbeenchecked.
TheorderofexecutionInordertotracetheorderofexecutionofthecallbacksassociatedwitheachhook,let’stakeapeekatthech4/ts/life-cycle/app.tsexample:
@Component({
selector:'panel',
inputs:['title','caption'],
template:'<ng-content></ng-content>'
})
classPanel{
ngOnChanges(changes){…}
ngOnInit(){…}
ngDoCheck(){…}
ngOnDestroy(){…}
ngAfterContentInit(){…}
ngAfterContentChecked(){…}
ngAfterViewInit(){…}
ngAfterViewChecked(){…}
}
ThePanelcomponentimplementsallthehookswithoutexplicitlyimplementingtheinterfacesassociatedwiththem.
Wecanusethecomponentinthefollowingtemplate:
<button(click)="toggle()">Toggle</button>
<div*ngIf="counter%2==0">
<panelcaption="Samplecaption"title="Sample">Helloworld!</panel>
</div>
Intheprecedingexample,wehaveapanelandabutton.Uponeachclickonthebutton,thepanelwillbeeitherremovedorappendedtotheviewbythengIfdirective.
Duringtheapplicationinitialization,iftheresultofthe"counter%2==0"expressionisevaluatedtotrue,thengOnChangesmethodwillbeinvoked.Thishappensbecausethevaluesofthetitleandcaptionpropertiesaregoingtobesetforthefirsttime.
Rightafterthis,thengOnInitmethodwillbecalled,sincethecomponenthasbeeninitialized.Oncethecomponent’sinitializationiscompleted,thechangedetectionwillbetriggered,whichwillleadtotheinvocationofthengDoCheckmethodthatallowsustohookcustomlogicfordetectingchangesinthestate.
NoteNotethatyouarenotsupposedtoimplementbothngDoCheckandngOnChangesmethodsforthesamecomponent,sincetheyaremutuallyexclusive.Theexampleheredoesthisforlearningpurposesonly.
AfterthengDoCheckmethod,thecomponent’scontentwillbefollowedbyperformingacheckonit(ngAfterContentInitandngAfterContentCheckedwillbeinvokedinthisorder).Rightafterthis,thesamewillhappenforthecomponent’sview(ngAfterViewInitfollowedbyngAfterViewChecked).
OncetheexpressionofthengIfdirectiveisevaluatedtofalse,theentirecomponentwillbedetachedfromtheview,whichwillleadtotheinvocationofthengOnDestroyhook.
Onthenextbuttonclick,ifthevalueoftheexpressionofngIfisequaltotrue,thesamesequenceofcallsofthelifecyclehooksastheoneduringtheinitializationphasewillbeexecuted.
DefininggenericviewswithTemplateRefWearealreadyfamiliarwiththeconceptsofinputs,content,andviewchildren,andwealsoknowwhenwecangetareferencetotheminthecomponent’slifecycle.Now,wewillcombinethemandintroduceanewconcept:TemplateRef.
Let’stakeastepbackandtakealookatthelastto-doapplicationwedevelopedearlierinthischapter.Inthefollowingscreenshot,youcanseewhatitsUIlookslike:
Ifwetakealookatitsimplementationinch4/ts/inputs-outputs/app.ts,we’llseethatthetemplateusedtorendertheindividualto-doitemsisdefinedinsidethetemplateoftheentireto-doapplication.
Whatifwewanttouseadifferentlayouttorendertheto-doitems?WecandothisbycreatinganothercomponentcalledTodo,whichencapsulatestheresponsibilityofrenderingthem.Then,wecandefineseparateTodocomponentsforthedifferentlayoutswewanttosupport.Thisway,weneedtohavendifferentcomponentsforndifferentlayouts,eventhoughweuseonlytheirtemplates.
Angular2comeswithamoreelegantsolution.Earlierinthischapter,wealreadydiscussedthetemplateelement.WesaidthatitallowsustodefineachunkofHTMLthatwillnotbeprocessedbythebrowser.Angular2allowsustoreferencesuchtemplateelementsandusethembypassingthemascontentchildren!
Hereishowwecanpassthecustomlayouttoourrefactoredtodo-appcomponent:
//ch4/ts/template-ref/app.ts
<todo-app>
<templatevar-todo>
<inputtype="checkbox"[checked]="todo.completed"
(change)="todo.completed=!todo.completed;">
<span[class.completed]="todo.completed">
{{todo.label}}
</span><br>
</template>
</todo-app>
Inthetemplate,wedeclareavariablecalledtodo.Laterinthetemplate,wecanuseittospecifythewayinwhichwewanttovisualizethecontent.
Now,let’sseehowwecangetareferencetothistemplateinthecontrolleroftheTodoAppcomponent:
//ch4/ts/template-ref/app.ts
classTodoApp{
@ContentChild(TemplateRef)
privateitemsTemplate:TemplateRef;
//…
}
AllwedohereisdefineapropertycalleditemsTemplateanddecorateitwiththe@ContentChilddecorator.Duringthecomponent’slifecycle(moreaccurately,inngAfterContentInit),thevalueofitemsTemplatewillbesetasareferenceofthetemplatethatwepassedasthecontentofthetodo-appelement.
Thereisonemoreproblemthough—weneedthetemplateintheTodoListcomponent,sincethat’stheplacewherewerendertheindividualto-doitems.WhatwecandoisdefineanotherinputoftheTodoListcomponentandpassthetemplatedirectlyfromTodoApp:
//ch4/ts/template-ref/app.ts
classTodoList{
@Input()todos:Todo[];
@Input()itemsTemplate:TemplateRef;
@Output()toggle=newEventEmitter<Todo>();
}
WeneedtopassitasaninputfromthetemplateofTodoApp:
...
<todo-list[todos]="todos"
[itemsTemplate]="itemsTemplate">
</todo-list>
TheonlythingleftistousethistemplatereferenceinthetemplateoftheTodoListapplication:
<!--…-->
<template*ngFor="vartodooftodos;template:itemsTemplate"></template>
WeexplainedtheextendedsyntaxofthengForOfdirectiveintheprevioussectionsofthischapter.Thissnippetshowsonemorepropertyofthisdirectivethatwecanset:thengForTemplateproperty.Bydefault,thetemplateofthengForOfdirectiveistheelementitisusedon.ByspecifyingatemplatereferencetothengForTemplateproperty,wecanusethepassedTemplateRefinstead.
UnderstandingandenhancingthechangedetectionWealreadybrieflydescribedthechangedetectionmechanismoftheframework.WesaidthatcomparedtoAngularJS1.x,whereitrunsinthecontextofthescope,inAngular2,itrunsinthecontextoftheindividualcomponents.Anotherconceptwementionedisthezones,whichbasicallyinterceptalltheasynchronouscallsthatwecanmakeusingthebrowserAPIsandprovideexecutioncontextforthechangedetectionmechanismoftheframework.ZonesfixtheannoyingproblemwehaveinAngularJS1.x,wherewhenweuseAPIsoutsideofAngular,weneedtoexplicitlyinvokethedigestloop.
InChapters1,GettingStartedwithAngular2andChapter2,TheBuildingBlocksofanAngular2Application,wediscussedthattherearetwomainimplementationsofthechangedetector:DynamicChangeDetectorandJitChangeDetector.ThefirstoneworksgreatforenvironmentswithstrictCSP(Content-Security-Policy)becauseofthedisableddynamicevaluationofJavaScript.Thesecondonetakesgreatbenefitsfromtheinline-cachingmechanismoftheJavaScriptvirtualmachineandthereforebringsgreatperformance!
Inthissection,we’llexploreanotherpropertyofthe@Componentdecorator’sconfigurationobject,whichprovidesusfurthercontroloverthechangedetectionmechanismoftheframeworkbychangingitsstrategy.Byexplicitlysettingthestrategy,weareabletopreventthechangedetectionmechanismfromrunningoveracomponent’ssubtrees,whichinsomecasescanbringgreatperformancebenefits.
TheorderofexecutionofthechangedetectorsNow,let’sbrieflydescribetheorderinwhichthechangedetectorsareinvokedinagivencomponenttree.
Forthispurpose,wewillusethelastimplementationoftheto-doapplicationwehave,butthistime,we’llextractthelogictorendertheindividualto-doitemsintoaseparatecomponentcalledTodoItem.Inthefollowingfigure,wecanseetheapplication’sstructure:
AtthetoplevelistheTodoAppcomponent,whichhastwochildren:InputBoxandTodoList.TheTodoListcomponentrenderstheindividualto-doitemsinTodoItemcomponents.Theimplementationdetailsarenotimportantforourpurpose,sowearegoingtoignorethem.
Now,weneedtorealizethatthereisanimplicitdependencybetweenthestateoftheparentcomponentanditschildren.Forinstance,thestateoftheTodoListcomponentdependscompletelyontheto-doitemsthatarelocatedatitsparent:theTodoAppcomponent.There’sasimilardependencybetweenTodoItemandTodoList,sincetheTodoListcomponentpassestheindividualto-doitemstoaseparateinstanceoftheTodoItemcomponent.
Becauseofourlastobservation,theorderofexecutionofthechangedetectorsattachedtotheindividualcomponentsisliketheoneshownontheprecedingfigure.Oncethechangedetectionmechanismrun,initiallyitwillperformacheckovertheTodoAppcomponent.Rightafterthis,theInputBoxcomponentwillbecheckedforchanges,followedbytheTodoListcomponent.Intheend,AngularwillinvokethechangedetectoroftheTodoItemcomponent.
Youcantracetheorderofexecutioninthech4/ts/change_detection_strategy_order/app.tsexample,whereeachindividualcomponentlogsamessageonceitsngDoCheckmethodisinvoked.
NoteNotethatonlythecomponentshaveaninstanceofachangedetectorattachedtothem;directivesusethechangedetectoroftheirparentcomponent.
ChangedetectionstrategiesThechangedetectionstrategiesthatAngular2providesare:CheckOnce,Checked,CheckAlways,Detached,Default,andOnPush.WewilldescribehowwecantakeadvantageofOnPushindetail,sinceitisverypowerfulwhenworkingwithimmutabledata.BeforetakingadeepdiveintoOnPush,let’sbrieflydescribetheotherstrategies.
Now,let’simporttheTypeScriptenum,whichcanbeusedtoconfigurethestrategyusedfortheindividualcomponents:
//ch4/ts/change_detection_strategy_broken/app.ts
import{ChangeDetectionStrategy}from'angular2/core';
Now,wecanconfiguretheTodoListcomponenttousetheCheckedstrategy:
@Component({
selector:'todo-list',
changeDetection:ChangeDetectionStrategy.Checked,
template:`...`,
styles:[…]
})
classTodoList{…}
Thisway,thechangedetectionwillbeskippeduntilitsmode(strategy)changestoCheckOnce.Butwhatdoesitmeantopreventthechangedetectionfromrunning?Youcangotohttp://localhost:5555/dist/dev/ch4/ts/change_detection_strategy_broken/andseetheinconsistentbehavioroftheTodoListcomponent.Whenyouaddanewto-doitemintheinputandyouclickonthebutton,itwon’timmediatelyappearinthelist.
Now,let’stryCheckOnce!Insidech4/ts/change_detection_strategy_broken/app.ts,changethechangedetectionstrategyoftheTodoListcomponenttoChangeDetectionStrategy.CheckOnce.Afterrefreshingthebrowser,trytoaddanewto-doitem.ThechangeshouldnotbeimmediatelyreflectedbecauseCheckOncewillinstructthechangedetectortoperformthecheckonlyonce(inthiscase,duringinitialization),andafterthat,nothingwillhappen.
Bydefault,itisusedintheCheckAlwaysmode,whichasitsnamestates,doesn’tpreventthechangedetectorfromrunning.
IfwedeclarethestrategyofagivencomponenttoDetached,thechangedetectorsubtreewillnotbeconsideredasapartofthemaintreeandwillbeskipped.
PerformanceboostingwithimmutabledataandOnPushThelastchangedetectionstrategythatwearegoingtodescribeisOnPush.Itisextremelyusefulwhentheresultthatthegivencomponentproducesdependsonlyonitsinputs.Insuchcases,wecanpassimmutabledatatotheinputsinordertomakesurethatitwillnotbemutatedbyanyothercomponent.Thisway,byhavingacomponentthatdependsonlyonitsimmutableinputs,wecanmakesurethatitproducesdifferentuserinterfacesonlyonceitreceivesdifferentinputs(thatis,differentreference).
Inthissection,wearegoingtoapplytheOnPushstrategyontheTodoListcomponent.Sinceitdependsonlyonitsinputs(thetodosinput),wewanttomakesurethatitschangedetectionwillbeperformedonlyonceitreceivesanewreferenceofthetodoscollection.
Theessenceofimmutabledataisthatitcannotchange.Thismeansthatonceweaddanewto-doitemtothetodoscollection,wecannotchangeit;instead,theadd(orinourcase,push)methodwillreturnanewcollection—acopyoftheinitialcollectionwiththenewitemincluded.
Thismayseemlikeahugeoverhead—tocopytheentirecollectiononeachchange.Inbigapplications,thismayhaveabigperformanceimpact.However,wedon’tneedtocopytheentirecollection.Therearelibrariesthatimplementimmutabledatastructureusingsmarteralgorithms:persistentdatastructures.Persistentdatastructuresareoutofthescopeofthecurrentcontent.Furtherinformationaboutthemcanbefoundinmostcomputersciencetextbooksforadvanceddatastructures.Thegoodthingisthatwedon’thavetounderstandtheirimplementationindepthinordertousethem!ThereisalibrarycalledImmutable.jsthatimplementsafewcommonlyusedimmutabledatastructures.Inourcase,wearegoingtousetheimmutablelist.Generally,theimmutablelistbehavesjustlikeanormallist,butoneachoperationthatissupposedtomutateit,itreturnsanewlist.
Thismeansthatifwehavealistcalledfoo,whichisimmutable,andweappendanewitemtothelist,wearegoingtogetanewreference:
letfoo=List.of(1,2,3);
letchanged=foo.push(4);
foo===changed//false
console.log(foo.toJS());//[1,2,3]
console.log(changed.toJS());//[1,2,3,4]
Inordertotakeadvantageofimmutability,weneedtoinstallImmutable.jsusingnpm.
We’vealreadydonethisinch4/ts/change_detection_strategy/app.ts.Immutable.jsisalreadypartofpackage.json,whichislocatedattherootdirectoryoftheproject.
Now,it’stimetorefactorourto-doapplicationandmakeituseimmutabledata!
UsingimmutabledatastructuresinAngularLet’stakealookathowwecurrentlykeeptheto-doitemsintheTodoAppcomponent:
classTodoApp{
todos:Todo[]=[...];
...
}
WeuseanarrayofTodoitems.TheJavaScriptarrayismutable,whichmeansthatifwepassittoacomponentthatusestheOnPushstrategy,itisnotsafetoskipthechangedetectionincasewegetthesameinputreference.Forinstance,wemayhavetwocomponentsthatusethesamelistofto-doitems.Bothcomponentscanmodifythelistsinceitismutable.Thiswillleadtoaninconsistentstatetoanyofthecomponentsincasetheirchangedetectionisnotperformed.That’swhyweneedtomakesurethatthelistthatholdstheitemsisimmutable.AllweneedtodointheTodoAppcomponentinordertomakesurethatitholdsitsdatainanimmutabledatastructureisthis:
//ch4/ts/change_detection_strategy/app.ts
classTodoApp{
todos:ImmutableList<Todo>=ImmutableList.of({
label:'Buymilk',
completed:false
},{
label:'Savetheworld',
completed:false
});
...
}
Inthisway,weconstructthetodospropertyasanimmutablelist.Sincethemutationoperationsoftheimmutablelistreturnanewlist,weneedtomakeaslightmodificationinaddTodoandtoggleTodoCompletion:
...
addTodo(label:string){
this.todos=this.todos.push({
label,
completed:false
});
}
toggleCompletion(index:number){
this.todos=this.todos.update(index,todo=>{
letnewTodo={
label:todo.label,
completed:!todo.completed
};
returnnewTodo;
});
}
…
TheaddTodofunctionlooksexactlythesameasbeforeexceptthatwesettheresultofthepushmethodasavaluetothetodosproperty.
IntoggleTodoCompletion,weusetheupdatemethodoftheimmutablelist.Asthefirstargument,wepasstheindexoftheto-doitemwewanttomodify,andthesecondargumentisacallbackthatdoestheactualmodification.Notethatsinceweareusingimmutabledatainthiscase,wecopythemodifiedto-doitem.Thisisrequiredbecauseittellstheupdatemethodthattheitemwiththegivenindexhasbeenchanged(sinceitisimmutable,itisconsideredaschangedonlywhenithasanewreference),whichmeansthattheentirelisthasbeenchanged.
Thatwasthecomplexpart!Nowlet’stakealookattheTodoListcomponent’sdefinition:
@Component({
selector:'todo-list',
changeDetection:ChangeDetectionStrategy.OnPush,
template:`...`,
styles:[...]
})
classTodoList{
@Input()todos:ImmutableList<Todo>;
@Output()toggle=newEventEmitter<number>();
toggleCompletion(index:number){
this.toggle.emit(index);
}
}
Insidethe@Componentdecorator,wesetthechangeDetectionpropertytothevalueoftheOnPushstrategy.Thismeansthatthecomponentwillrunitschangedetectoronlywhenanyofitsinputsgetsanewreference.ThetemplateofthecomponentstaysexactlythesamesincengForOfinternallyusesES2015iteratorstolooptheitemsintheprovidedcollection.TheyaresupportedbyImmutable.js,sothechangesinthetemplatearenotrequired.
Sinceweneedtheindexofthechangeditem(theoneweuseintheupdatemethodofthetodoscollectioninTodoApp),wechangethetypeoftheoutputofthecomponenttoEventEmitter<number>.IntoggleCompletion,weemittheindexofthechangedto-doitem.
Thisishowweoptimizedoursimpleto-doapplicationbypreventingthechangedetectionmechanismfromrunningintheentirerightsubtreeincasetheparentcomponenthasn’tpushedaninputwithanewreference.
SummaryInthischapter,wewentthroughthecorebuildingblocksofanAngular2application:directivesandcomponents.Webuiltacoupleofsamplecomponents,whichshowusthesyntaxtobeusedforthedefinitionofthesefundamentalconcepts.Wealsodescribedthelifecycleofeachdirectiveandthecoresetoffeaturesthegivendirectiveandcomponenthave.Asthenextstep,wesawhowwecanenhancetheperformanceofourapplicationbyusingtheOnPushchangedetectionstrategywithimmutabledata.
ThenextchapteriscompletelydedicatedtotheAngular2servicesandthedependencyinjectionmechanismoftheframework.Wearegoingtolookathowwecandefineandinstantiatecustominjectorsandhowwecantakeadvantageofthedependencyinjectionmechanisminourdirectivesandcomponents.
Chapter5.DependencyInjectioninAngular2Inthischapter,we’llexplainhowtotakeadvantageofthedependencyinjection(DI)mechanismoftheframeworkwithallitsvariousfeatures.
Wewillexplorethefollowingtopics:
Configuringandcreatinginjectors.Instantiatingobjectsusinginjectors.Injectingdependenciesintoourdirectivesandcomponents.Thisway,wewillbeabletoreusethebusinesslogicdefinedwithintheservicesandwireitupwiththeUIlogic.AnnotatingtheES5codewewillwriteinordertogettheexactsameresultwegetwhenweareusingtheTypeScriptsyntax.
WhydoIneedDependencyInjection?Let’ssupposethatwehaveaCarclassthatdependsontheEngineandTransmissionclasses.Howcanweimplementthissystem?Let’stakealook:
classEngine{…}
classTransmission{…}
classCar{
engine;
transmission;
constructor(){
this.engine=newEngine();
this.transmission=newTransmission();
}
}
Inthisexample,wecreatedthedependenciesoftheCarclassinsideofitsconstructor.Althoughitlookssimple,itisfarfrombeingflexible.EachtimewecreateaninstanceoftheCarclass,instancesofthesameEngineandTransmissionclasseswillbecreated.Thismaybeproblematicbecauseofthefollowingreasons:
TheCarclassgetslesstestablebecausewecan’ttestitindependentlyfromitsengineandtransmissiondependencies.WecoupletheCarclasswiththelogicusedfortheinstantiationofitsdependencies.
DependencyInjectioninAngular2AnotherwaywecanapproachthisisbytakingadvantageoftheDIpattern.We’realreadyfamiliarwithitfromAngularJS1.x.Let’sdemonstratehowwecanrefactortheprecedingcodeusingDIinthecontextofAngular2:
classEngine{…}
classTransmission{…}
@Injectable()
classCar{
engine;
transmission;
constructor(engine:Engine,transmission:Transmission){
this.engine=engine;
this.transmission=transmission;
}
}
Allwedidintheprecedingsnippetwasaddthe@InjectableclassdecoratorontopofthedefinitionoftheCarclassandprovidetypeannotationsfortheparametersofitsconstructor.
BenefitsofDIinAngular2Thereisonemorestepleft,whichwe’lltakealookatinthenextsection.Butlet’sseewhatthebenefitsofthementionedapproachare:
WecaneasilypassdifferentversionsofthedependenciesoftheCarclassforatestingenvironment.We’renotcoupledwiththelogicaroundthedependencies’instantiation.
TheCarclassisonlyresponsibleforimplementingitsowndomain-specificlogicinsteadofbeingcoupledwithadditionalfunctionalities,suchasthemanagementofitsdependencies.Ourcodealsogotmoredeclarativeandeasiertoread.
Now,afterwe’verealizedsomeofthebenefitsoftheDI,let’stakealookatthemissingpiecesinordertomakethiscodework!
ConfiguringaninjectorTheprimitiveusedfortheinstantiationoftheindividualdependenciesinourAngular2applicationsviatheDImechanismoftheframeworkiscalledtheinjector.Theinjectorcontainsasetofprovidersthatencapsulatethelogicfortheinstantiationofregistereddependenciesassociatedwithtokens.Wecanthinkoftokensasidentifiersofthedifferentprovidersregisteredwithintheinjector.
Let’stakealookatthefollowingsnippet,whichislocatedatch5/ts/injector-basics/injector.ts:
import'reflect-metadata';
import{
Injector,Inject,Injectable,
OpaqueToken,provide
}from'angular2/core';
constBUFFER_SIZE=newOpaqueToken('buffer-size');
classBuffer{
constructor(@Inject(BUFFER_SIZE)privatesize:Number){
console.log(this.size);
}
}
@Injectable()
classSocket{
constructor(privatebuffer:Buffer){}
}
letinjector=Injector.resolveAndCreate([
provide(BUFFER_SIZE,{useValue:42}),
Buffer,
Socket
]);
injector.get(Socket);
Youcanrunthefileusingthefollowingcommand:
cdapp
ts-nodech5/ts/injector-basics/injector.ts
Ifyouhaven’tinstalledts-nodeyet,takealookatChapter3,TypeScriptCrashCourse,whichexplainshowyoucanproceedinordertohaveitupandrunningonyourcomputer.
WeimportInjector,Injectable,Inject,OpaqueToken,andprovide.
Injectorrepresentsthecontainerusedfortheinstantiationofthedifferentdependencies.UsingtherulesdeclaredwiththeprovidefunctionandthemetadatageneratedbytheTypeScriptcompiler,itknowshowtocreatethem.
Intheprecedingsnippet,weinitiallydefinedtheBUFFER_SIZEconstantandsetittothenewOpaqueToken('buffer-size')value.WecanthinkofthevalueofBUFFER_SIZEasa
uniquevaluethatcannotbeduplicatedintheapplication(OpaqueTokenisanalternativeoftheSymbolclassfromES2015,sinceatthetimeofwritingthis,itisnotsupportedbyTypeScript).
Wedefinedtwoclasses:BufferandSocket.TheBufferclasshasaconstructorthatacceptsonlyasingledependencycalledsize,whichisofthetypeNumber.Inordertoaddadditionalmetadatafortheprocessofdependencyresolution,weusethe@Injectparameterdecorator.Thisdecoratoracceptsanidentifier(alsoknownastoken)ofthedependencywewanttoinject.Usually,itisthetypeofthedependency(thatis,areferenceofaclass),butinsomecases,itcanbeadifferenttypeofavalue.Forexample,inourcase,weusedtheinstanceoftheOpaqueTokenclass.
DependencyresolutionwithgeneratedmetadataNowlet’stakealookattheSocketclass.Wedecorateitwiththe@Injectabledecorator.ThisdecoratorissupposedtobeusedbyanyclassthatacceptsdependenciesthatshouldbeinjectedviathedependencyinjectionmechanismofAngular2.
The@InjectabledecoratorforcestheTypeScriptcompilertogenerateadditionalmetadataforthetypesofdependenciesthatagivenclassaccepts.Thismeansthatifweomitthe@Injectabledecorator,Angular’sDImechanismwillnotbeawareofthetokensassociatedwiththedependenciesitneedstoresolve.
TypeScriptdoesn’tgenerateanymetadataifnodecoratorisusedontopofaclassmostlyforperformanceconcerns.Imagineifsuchmetadatawasgeneratedforeachindividualclassthatacceptsdependencies—inthiscase,theoutputwouldbebloatedwithadditionaltypemetadatathatwouldbeunused.
Analternativetousing@Injectableistoexplicitlydeclarethetypesofdependenciesusingthe@Injectdecorator.Takealookatthefollowing:
classSocket{
constructor(@Inject(Buffer)privatebuffer:Buffer){}
}
Thismeansthattheprecedingcodehasequivalentsemanticstothecodethatuses@Injectable,asmentionedearlier.TheonlydifferenceisthatAngular2willgetthetypeofdependency(thatis,thetokenassociatedwithit)explicitly(directlyfromthemetadataaddedbythe@Injectordecorator)comparedtothecasewhere@Injectableisused,whenitwilllookatthemetadatageneratedbythecompiler.
InstantiatinganinjectorNow,let’screateaninstanceofaninjectorinordertouseitfortheinstantiationofregisteredtokens:
letinjector=Injector.resolveAndCreate([
provide(BUFFER_SIZE,{useValue:42}),
Buffer,
Socket
]);
WecreateaninstanceoftheInjectorusingitsstaticmethodcalledresolveAndCreate.ThisisafactorymethodthatacceptsanarrayofprovidersasargumentandreturnsanewInjector.
resolvemeansthattheproviderswillgothrougharesolutionprocess,whichincludessomeinternalprocessing(flatteningmultiplenestedarraysandconvertingindividualprovidersintoanarray).Later,theinjectorcaninstantiateanyofthedependenciesforwhichwehaveregisteredprovidersbasedontherulestheprovidersencapsulate.
Inourcase,weusedtheprovidemethodinordertoexplicitlytelltheAngular2DImechanismtousethevalue42whentheBUFFER_SIZEtokenisrequired.Theothertwoprovidersareimplicit.Angular2willinstantiatethembyinvokingtheprovidedclasswiththenewoperatoroncealloftheirdependenciesareresolved.
WerequesttheBUFFER_SIZEvalueintheconstructoroftheBufferclass:
classBuffer{
constructor(@Inject(BUFFER_SIZE)privatesize:Number){
console.log(this.size);
}
}
Intheprecedingexample,weusedthe@Injectparameterdecorator.IthintstheDImechanismthatthefirstargumentoftheconstructoroftheBufferclassshouldbeinstantiatedwiththeproviderassociatedwiththeBUFFER_SIZEtokenpassedtotheinjector.
IntroducingforwardreferencesAngular2introducedtheconceptofforwardreferences.Itisrequiredduetothefollowingreasons:
ES2015classesarenothoisted.Allowresolutionofthedependenciesthataredeclaredafterthedeclarationofthedependentproviders.
Inthissection,we’regoingtoexplaintheproblemthatforwardreferencessolveandthewaywecantakeadvantageofthem.
Now,let’ssupposethatwehavedefinedtheBufferandSocketclassesintheoppositeorder:
//ch5/ts/injector-basics/forward-ref.ts
@Injectable()
classSocket{
constructor(privatebuffer:Buffer){…}
}
//undefined
console.log(Buffer);
classBuffer{
constructor(@Inject(BUFFER_SIZE)privatesize:Number){…}
}
//[Function:Buffer]
console.log(Buffer);
Here,wehavetheexactsamedependenciesasintheonesinthepreviousexample,butinthiscase,theSocketclassdefinitionprecedesthedefinitionoftheBufferclass.NotethatthevalueoftheBufferidentifierwillequalundefineduntiltheJavaScriptvirtualmachineevaluatesthedeclarationoftheBufferclass.However,themetadataforthetypesofdependenciesthatSocketacceptswillbegeneratedandplacedrightaftertheSocketclassdefinition.ThismeansthatalongwiththeinterpretationofthegeneratedJavaScript,thevalueoftheBuffertokenwillequalundefined—thatis,asatypeofdependency(orinthecontextoftheDImechanismofAngular2,itstoken),theframeworkwillgetaninvalidvalue.
Runningtheprecedingsnippetwillresultinaruntimeerrorofthefollowingform:
Error:CannotresolveallparametersforSocket(undefined).Makesurethey
allhavevalidtypeorannotations.
Thebestwaytoresolvethisissueisbyswappingthedefinitionswiththeirproperorder.AnotherwaywecanproceedistotakeadvantageofasolutionthatAngular2provides:aforwardreference:
…
import{forwardRef}from'angular2/core';
…
@Injectable()
classSocket{
constructor(@Inject(forwardRef(()=>Buffer))
privatebuffer:Buffer){}
}
classBuffer{…}
Theprecedingsnippetdemonstrateshowwecantakeadvantageofforwardreferences.Allweneedtodoisusethe@InjectparameterdecoratorandpasstheresultoftheinvocationoftheforwardReffunctiontoit.TheforwardReffunctionisahigher-orderfunctionthatacceptsasingleargument—anotherfunctionthatisresponsibleforreturningthetokenassociatedwiththedependency(ormorepreciselyassociatedwithitsprovider)thatneedstobeinjected.Thisway,theframeworkprovidesawaytodefertheprocessofresolvingthetypes(tokens)ofdependencies.
ThetokenofthedependencywillberesolvedthefirsttimeSocketneedstobeinstantiated,unlikethedefaultbehaviorinwhichthetokenisrequiredatthetimeofthedeclarationofthegivenclass.
ConfiguringprovidersNow,let’stakealookatanexamplesimilartotheoneusedearlierbutwithadifferentconfigurationoftheinjector:
letinjector=Injector.resolveAndCreate([
provide(BUFFER_SIZE,{useValue:42}),
provide(Buffer,{useClass:Buffer}),
provide(Socket,{useClass:Socket})
]);
Inthiscase,insideoftheprovider,weexplicitlydeclaredthatwewanttheBufferclasstobeusedfortheconstructionofthedependencywithatokenequaltothereferenceoftheBufferclass.WedotheexactsamethingforthedependencyassociatedwiththeSockettoken;butthistime,weprovidetheSocketclassinstead.ThisishowAngular2willproceedwhenweomitthecalloftheprovidefunctionandpassonlyareferencetoaclassinstead.
Explicitlydeclaringtheclassusedfortheinstantiationofthesameclassmayseemquiteworthless,andgiventheexampleswelookedatsofar,this’llbecompletelycorrect.Insomecases,however,wemightwanttoprovideadifferentclassfortheinstantiationofadependencyassociatedwithgivenclasstoken.
Forinstance,let’ssupposewehavetheHttpservicethatisusedinaservicecalledUserService:
classHttp{…}
@Injectable()
classUserService{
constructor(privatehttp:Http){}
}
letinjector=Injector.resolveAndCreate([
UserService,
Http
]);
TheUserServiceserviceusesHttpforcommunicationwithaRESTfulservice.WecaninstantiateUserServiceusinginjector.get(UserService).Thisway,theconstructorofUserServiceinvokedbytheinjector’sgetmethodwillacceptaninstanceoftheHttpserviceasanargument.However,ifwewanttotestUserService,wedon’treallyneedtomakeHTTPcallstotheRESTfulservice.Incaseofunittesting,wecanprovideadummyimplementationthatwillonlyfaketheseHTTPcalls.InordertoinjectaninstanceofadifferentclasstotheUserServiceservice,wecanchangetheconfigurationoftheinjectortothefollowing:
classDummyHttp{…}
//...
letinjector=Injector.resolveAndCreate([
UserService,
provide(Http,{useClass:DummyHttp})
]);
Now,whenweinstantiateUserService,it’sconstructorwillreceiveareferencetoaninstanceoftheDummyHttpservice.Thiscodeisavailableatch5/ts/configuring-providers/dummy-http.ts.
UsingexistingprovidersAnotherwaytoproceedisusingtheuseExistingpropertyoftheprovider’sconfigurationobject:
//ch5/ts/configuring-providers/existing.ts
letinjector=Injector.resolveAndCreate([
DummyService,
provide(Http,{useExisting:DummyService}),
UserService
]);
Intheprecedingsnippet,weregisteredthreetokens:DummyService,UserService,andHttp.WedeclaredthatwewanttobindtheHttptokentotheexistingtoken,DummyService.ThismeansthatwhentheHttpserviceisrequested,theinjectorwillfindtheproviderforthetokenusedasthevalueoftheuseExistingpropertyandinstantiateitorgetthevalueassociatedwithit.WecanthinkofuseExistingascreatinganaliasofthegiventoken:
letdummyHttp={
get(){},
post(){}
};
letinjector=Injector.resolveAndCreate([
provide(DummyService,{useValue:dummyHttp}),
provide(Http,{useExisting:DummyService}),
UserService
]);
console.assert(injector.get(UserService).http===dummyHttp);
TheprecedingsnippetwillcreateanaliasoftheHttptokentotheDummyHttptoken.ThismeansthatoncetheHttptokenisrequested,thecallwillbeforwardedtotheproviderassociatedwiththeDummyHttptoken,whichwillberesolvedtothevaluedummyHttp.
DefiningfactoriesforinstantiatingservicesNow,let’ssupposewewanttocreateacomplexobject,forexample,onethatrepresentsaTransportLayerSecurity(TLS)connection.Afewofthepropertiesofsuchanobjectareasocket,asetofcryptoprotocols,andacertificate.Inthecontextofthisproblem,thefeaturesoftheDImechanismofAngular2wehavesofarlookedatmightseemabitlimited.
Forexample,wemightneedtoconfiguresomeofthepropertiesoftheTLSConnectionclasswithoutcouplingtheprocessofitsinstantiationwithalltheconfigurationdetails(chooseappropriatecryptoalgorithms,opentheTCPsocketoverwhichwewillestablishthesecureconnection,andsoon).
Inthiscase,wecantakeadvantageoftheuseFactorypropertyoftheprovider’sconfigurationobject:
letinjector=Injector.resolveAndCreate([
provide(TLSConnection,{
useFactory:(socket:Socket,certificate:Certificate,crypto:Crypto)
=>{
letconnection=newTLSConnection();
connection.certificate=certificate;
connection.socket=socket;
connection.crypto=crypto;
socket.open();
returnconnection;
},
deps:[Socket,Certificate,Crypto]
}),
provide(BUFFER_SIZE,{useValue:42}),
Buffer,
Socket,
Certificate,
Crypto
]);
Theprecedingcodeseemsabitcomplexatfirst,butlet’stakealookatitstepbystep.Wecanstartwiththepartswe’realreadyfamiliarwith:
letinjector=Injector.resolveAndCreate([
...
provide(BUFFER_SIZE,{useValue:42}),
Buffer,
Socket,
Certificate,
Crypto
]);
Initially,weregisteredanumberofproviders:Buffer,Socket,Certificate,andCrypto.Justlikeintheprecedingexample,wealsoregisteredtheBUFFER_SIZEtokenand
associateditwiththevalue42.ThismeansthatwecanalreadycreateobjectsoftheBuffer,Socket,Certificate,andCryptotypes:
//bufferwithsize42
console.log(injector.get(Buffer));
//socketwithbufferwithsize42
console.log(injector.get(Socket));
WecancreateandconfigureaninstanceoftheTLSConnectionobjectinthefollowingway:
letconnection=newTLSConnection();
connection.certificate=certificate;
connection.socket=socket;
connection.crypto=crypto;
socket.open();
returnconnection;
Now,ifweregisteraproviderthathastheTLSConnectiontokenasadependency,wewillpreventthedependencyinjectionmechanismofAngularfromtakingcareofthedependencyresolutionprocess.Inordertohandlethisproblem,wecanusetheuseFactorypropertyoftheprovider’sconfigurationobject.Thisway,wecanspecifyafunctioninwhichwecanmanuallycreatetheinstanceoftheobjectassociatedwiththeprovider’stoken.WecanusetheuseFactorypropertytogetherwiththedepspropertyinordertospecifythedependenciestobepassedtothefactory:
provide(TLSConnection,{
useFactory:(socket:Socket,certificate:Certificate,crypto:Crypto)=>
{
//...
},
deps:[Socket,Certificate,Crypto]
})
Intheprecedingsnippet,wedefinedthefactoryfunctionusedfortheinstantiationofTLSConnection.Asdependencies,wedeclaredSocket,Certificate,andCrypto.ThesedependenciesareresolvedbytheDImechanismofAngular2andinjectedtothefactoryfunction.Youcantakealookattheentireimplementationandplaywithitatch5/ts/configuring-providers/factory.ts.
ChildinjectorsandvisibilityInthissection,we’regoingtotakealookathowwecanbuildahierarchyofinjectors.ThisisacompletelynewconceptintroducedbyAngular2.Eachinjectorcanhavezerooroneparentinjectorsandeachparentinjectorcanhavezeroormorechildren,respectively.IncontrasttoAngularJS1.x,wherealltheregisteredprovidersarestoredinaflatstructureinAngular2,theyarestoredinatree.Theflatstructureismorelimited;forinstance,itdoesn’tsupportthenamespacingoftokens;thatis,wecannotdeclaredifferentprovidersforthesametoken,whichmightberequiredinsomecases.Sofar,welookedatanexampleofinjectorthatdoesn’thaveanychildrenoraparent.Nowlet’sbuildahierarchyofinjectors!
Inordertogainabetterunderstandingofthishierarchicalstructureofinjectors,let’stakealookatthefollowingfigure:
Here,weseeatreewhereeachnodeisaninjectorandeachoftheseinjectorskeepsareferencetoitsparent.InjectorHousehasthreechildinjectors:Bathroom,Kitchen,andGarage.
Garagehastwochildren:CarandStorage.Wecanthinkoftheseinjectorsascontainerswithregisteredprovidersinsideofthem.
Let’ssupposewewanttogetthevalueoftheproviderassociatedwiththetokenTire.IfweusetheinjectorCar,thismeansthatAngular2’sDImechanismwilltrytofindtheproviderassociatedwiththistokeninCarandallofitsparents,GarageandHouse,untilitfindsit.
BuildingahierarchyofinjectorsInordertogainabetterunderstandingofthepreviousparagraph,let’stakealookatthissimpleexample:
//ch5/ts/parent-child/simple-example.ts
classHttp{}
@Injectable()
classUserService{
constructor(publichttp:Http){}
}
letparentInjector=Injector.resolveAndCreate([
Http
]);
letchildInjector=parentInjector.resolveAndCreateChild([
UserService
]);
//UserService{http:Http{}}
console.log(childInjector.get(UserService));
//true
console.log(childInjector.get(Http)===parentInjector.get(Http));
Theimportsareomitted,sincetheyarenotessentialtoexplaintheprecedingsnippet.Wehavetwoservices,HttpandUserService,whereUserServicedependsontheHttpservice.
Initially,wecreatedaninjectorusingtheresolveAndCreatestaticmethodoftheInjectorclass.Wepassedanimplicitprovidertothisinjector,whichwilllaterberesolvedtoaproviderwithanHttptoken.UsingresolveAndCreateChild,weresolvedthepassedprovidersandinstantiatedaninjector,whichpointstoparentInjector(sowegetthesamerelationastheonebetweenGarageandHouseonthediagramabove).
Now,usingchildInjector.get(UserService),weareabletogetthevalueassociatedwiththeUserServicetoken.Similarly,usingchildInjector.get(Http)andparentInjector.get(Http),wegetthesamevalueassociatedwiththeHttptoken.ThismeansthatchildInjectorasksitsparentforthevalueassociatedwiththerequestedtoken.
However,ifwetrytouseparentInjector.get(UserService),wewon’tbeabletogetthevalueassociatedwiththetoken,sinceinthisinjector,wedon’thavearegisteredproviderwiththistoken.
ConfiguringdependenciesNowthatwe’refamiliarwiththeinjectors’hierarchy,let’sseehowwecangetthedependenciesfromtheappropriateinjectorsinit.
Usingthe@SelfdecoratorNowlet’ssupposewehavethefollowingconfiguration:
abstractclassChannel{}
classHttpextendsChannel{}
classWebSocketextendsChannel{}
@Injectable()
classUserService{
constructor(publicchannel:Channel){}
}
letparentInjector=Injector.resolveAndCreate([
provide(Channel,{useClass:Http})
]);
letchildInjector=parentInjector.resolveAndCreateChild([
provide(Channel,{useClass:WebSocket}),
UserService
]);
WecaninstantiatetheUserServicetokenusing:
childInjector.get(UserService);
InUserService,wecandeclarethatwewanttogettheChanneldependencyfromthecurrentinjector(thatis,childInjector)usingthe@Selfdecorator:
@Injectable()
classUserService{
constructor(@Self()publicchannel:Channel){}
}
AlthoughthisisgoingtobethedefaultbehaviorduringtheinstantiationoftheUserService,using@Self,wecanbemoreexplicit.Let’ssupposewechangetheconfigurationofchildInjectortothefollowing:
letparentInjector=Injector.resolveAndCreate([
provide(Channel,{useClass:Http})
]);
letchildInjector=parentInjector.resolveAndCreateChild([
UserService
]);
Ifwekeepthe@SelfdecoratorintheUserServiceconstructorandtrytoinstantiateUserServiceusingchildInjector,wewillgetaruntimeerrorbecauseofthemissingproviderforChannel.
Skippingtheselfinjector
Insomecases,especiallywhileinjectingthedependenciesofUIcomponents,wemaywanttousetheproviderregisteredintheparentinjectorinsteadoftheoneregisteredinthecurrentinjector.Wecanachievethisbehaviorbytakingadvantageofthe@SkipSelfdecorator.Forinstance,let’ssupposewehavethefollowingdefinitionoftheclassContext:
classContext{
constructor(publicparentContext:Context){}
}
EachinstanceoftheContextclasshasaparent.Nowlet’sbuildahierarchyoftwoinjectors,whichwillallowustocreateacontextwithaparentcontext:
letparentInjector=Injector.resolveAndCreate([
provide(Context,{useValue:newContext(null)})
]);
letchildInjector=parentInjector.resolveAndCreateChild([
Context
]);
Sincetherootcontextdoesn’thaveaparent,wewillsetthevalueofitsprovidertobenewContext(null).
Ifwewanttoinstantiatethechildcontext,wecanuse:
childInjector.get(Context);
Fortheinstantiationofthechild,ContextwillbeusedbytheproviderregisteredwithinthechildInjector.However,asadependencyitacceptsanobjectwhichisaninstanceoftheContextclass.Suchclassesexistinthesameinjector,whichmeansthatAngularwilltrytoinstantiateit,butithasadependencyoftheContexttype.Thisprocesswillleadtoaninfiniteloopthatwillcausearuntimeerror.
Inordertopreventitfromhappening,wecanchangethedefinitionofContextinthefollowingway:
classContext{
constructor(@SkipSelf()publicparentContext:Context){}
}
Theonlychangethatweintroducedistheadditionoftheparameterdecorator@SkipSelf.
HavingoptionaldependenciesAngular2introducesthe@Optionaldecorator,whichallowsustodealwithdependenciesthatdon’thavearegisteredproviderassociatedwiththem.Supposeadependencyofaproviderisnotavailableinanyofthetargetinjectorsresponsibleforitsinstantiation.Ifweusethe@Optionaldecorator,duringtheinstantiationofthedependentproviderforvalueofthemissingdependencywillbepassednull.
Nowlet’stakealookatthefollowingexample:
abstractclassSortingAlgorithm{
abstractsort(collection:BaseCollection):BaseCollection;
}
@Injectable()
classCollectionextendsBaseCollection{
privatesort:SortingAlgorithm;
constructor(sort:SortingAlgorithm){
super();
this.sort=sort||this.getDefaultSort();
}
}
letinjector=Injector.resolveAndCreate([
Collection
]);
Inthiscase,wedefinedanabstractclasscalledSortingAlgorithmandaclasscalledCollection,whichacceptsaninstanceofaconcreteclassasadependencythatextendsSortingAlgorithm.InsideoftheCollectionconstructor,wesetthesortinstancepropertytothepasseddependencyoftheSortingAlgorithmtypeoradefaultsortingalgorithmimplementation.
Wedidn’tdefineanyprovidersfortheSortingAlgorithmtokenintheinjectorweconfigured.So,ifwewanttogetaninstanceoftheCollectionclassusinginjector.get(Collection),we’llgetaruntimeerror.ThismeansthatifwewanttogetaninstanceoftheCollectionclassusingtheDImechanismoftheframework,wemustregisteraproviderfortheSortingAlgorithmtoken,althoughwecanfallbacktothedefaultsortingalgorithm’simplementation.
Angular2providesasolutiontothisproblemwiththe@Optionaldecorator.
Thisishowwecanapproachtheproblemusingthe@Optionaldecoratorprovidedbytheframework:
//ch5/ts/decorators/optional.ts
@Injectable()
classCollectionextendsBaseCollection{
privatesort:SortingAlgorithm;
constructor(@Optional()sort:SortingAlgorithm){
super();
this.sort=sort||this.getDefaultSort();
}
}
Intheprecedingsnippet,wedeclaredthesortdependencyasoptional,whichmeansthatifAngular2doesn’tfindanyproviderforitstoken,itwillpassthenullvalue.
UsingmultiprovidersMultiprovidersareanothernewconceptbroughttotheAngular2DImechanism.Theyallowustoassociatemultipleproviderswiththesametoken.Thiscanbequiteusefulifwe’redevelopingathird-partylibrarythatcomeswithsomedefaultimplementationsofdifferentservices,butyouwanttoallowtheuserstoextenditwithcustomones.TheyarealsoexclusivelyusedtodeclaremultiplevalidationsoverasinglecontrolintheAngular2
formmodule.WewillexplainthismoduleinChapter6,WorkingwiththeAngular2RouterandForms,andChapter7,ExplainingPipesandCommunicatingwithRESTfulServices.
AnothersampleofanapplicableusecaseofmultiprovidersiswhatAngular2usesforeventmanagementintheirWebWorkersimplementation.Theycreatemultiprovidersforeventmanagementplugins.Eachoftheprovidersreturnsadifferentstrategy,whichsupportsadifferentsetofevents(touchevents,keyboardevents,andsoon).Onceagiveneventoccurs,theycanchoosetheappropriatepluginthathandlesit.
Let’stakealookatanexamplethatillustratesthetypicalusageofmultiproviders:
//ch5/ts/configuring-providers/multi-providers.ts
constVALIDATOR=newOpaqueToken('validator');
interfaceEmployeeValidator{
(person:Employee):boolean;
}
classEmployee{...}
letinjector=Injector.resolveAndCreate([
provide(VALIDATOR,{multi:true,
useValue:(person:Employee)=>{
if(!person.name){
return'Thenameisrequired';
}
}
}),
provide(VALIDATOR,{multi:true,
useValue:(person:Employee)=>{
if(!person.name||person.name.length<1){
return'Thenameshouldbemorethan1symbollong';
}
}
}),
Employee
]);
Intheprecedingsnippet,wedeclaredaconstantcalledVALIDATORwithanewinstanceofOpaqueToken.Wealsocreatedaninjectorwhereweregisteredthreeproviders—twoofthemareusedasvaluefunctionsthat,basedondifferentcriteria,validateinstancesoftheclassEmployee.ThesefunctionsareofthetypeEmployeeValidator.
InordertodeclarethatwewanttheinjectortopassalltheregisteredvalidatorstotheconstructoroftheclassEmployee,weneedtousethefollowingconstructordefinition:
classEmployee{
name:string;
constructor(@Inject(VALIDATOR)privatevalidators:EmployeeValidator[])
{}
validate(){
returnthis.validators
.map(v=>v(this))
.filter(value=>!!value);
}
}
Intheprecedingexample,wedeclaredaclassEmployeethatacceptsasingledependency—anarrayofEmployeeValidators.Inthemethodvalidate,weappliedtheindividualvalidatorsoverthecurrentclassinstanceandfilteredtheresultsinordertogetonlytheonesthathavereturnedanerrormessage.
NoticethattheconstructorargumentvalidatorsisoftheEmployeeValidator[]type.Sincewecan’tusethetype“arrayofobjects”asatokenforaprovider,becauseitisnotavalidtypereference,weneedtousethe@Injectparameterdecorator.
UsingDIwithcomponentsanddirectivesInChapter4,GettingStartedwithAngular2ComponentsandDirectives,whenwedevelopedourfirstAngular2directive,wesawhowwecantakeadvantageoftheDImechanismtoinjectservicesintoourUI-relatedcomponents(thatis,directivesandcomponents).
Let’stakeaquicklookatwhatwedidearlier,butfromaDIperspective:
//ch4/ts/tooltip/app.ts
//...
@Directive(...)
exportclassTooltip{
@Input()
saTooltip:string;
constructor(privateel:ElementRef,privateoverlay:Overlay){
this.overlay.attach(el.nativeElement);
}
//...
}
@Component({
//...
providers:[Overlay],
directives:[Tooltip]
})
classApp{}
Mostofthecodefromtheearlierimplementationisomittedbecauseitisnotdirectlyrelatedtoourcurrentfocus.
NotethattheconstructorofTooltipacceptstwodependencies:
AninstanceoftheElementRefclass.AninstanceoftheOverlayclass.
Thetypesofdependenciesarethetokensassociatedwiththeirproviders,andthecorrespondingvaluesfromtheprovidersaregoingtobeinjectedwiththeDImechanismofAngular2.
AlthoughthedeclarationofthedependenciesoftheTooltipclasslooksexactlythesameaswhatwedidintheprevioussections,there’sneitheranyexplicitconfigurationnoranyinstantiationofaninjector.
IntroducingtheelementinjectorsUnderthehood,Angularwillcreateinjectorsforallthedirectivesandcomponents,andaddadefaultsetofproviderstoit.Thisistheso-calledelementinjectorandissomethingtheframeworktakescareofitself.Theinjectorsassociatedwiththecomponentsarecalledhostinjectors.OneoftheprovidersineachdirectiveandcomponentinjectorisassociatedwiththeElementReftoken;itwillreturnareferencetothehostelementofthedirective.ButwhereistheproviderfortheOverlayclassdeclared?Let’stakealookattheimplementationofthetop-levelcomponent:
@Component({
//...
providers:[Overlay],
directives:[Tooltip]})
classApp{}
WeconfiguredtheelementinjectorfortheAppcomponentbydeclaringtheproviderspropertyinsideofthe@Componentdecorator.Atthispoint,theregisteredprovidersaregoingtobevisiblebythedirectiveorthecomponentassociatedwiththecorrespondingelementinjectorandthecomponent’sentirecomponentsubtree,unlessitisoverriddensomewhereinthehierarchy.
DeclaringprovidersfortheelementinjectorsHavingthedeclarationofalltheprovidersinthesameplacemightbequiteinconvenient.Forexample,imaginewe’redevelopingalarge-scaleapplicationthathashundredsofcomponentsdependingonthousandsofservices.Inthiscase,configuringalltheprovidersintherootcomponentisnotapracticalsolution.Therewillbenamecollisionswhentwoormoreprovidersareassociatedtothesametoken.Theconfigurationwillbehugeanditwillbehardtotracewherethedifferenttokensneedtobeinjected.
Aswementioned,Angular2’s@Directive(andrespectively@Component)decoratorallowsustointroducedirective-specificprovidersusingtheprovidersproperty.Hereishowwecanapproachthis:
@Directive({
selector:'[saTooltip]',
providers:[OverlayMock]
})
exportclassTooltip{
@Input()
saTooltip:string;
constructor(privateel:ElementRef,privateoverlay:Overlay){
this.overlay.attach(el.nativeElement);
}
//...
}
//...
bootstrap(App);
TheprecedingexampleoverridestheproviderfortheOverlaytokenintheTooltipdirective’sdeclaration.Thisway,AngularwillinjectaninstanceofOverlayMockinsteadofOverlayduringtheinstantiationofthetooltip.
Abetterwaytooverridetheproviderisusingthebootstrapfunction.Wecandothefollowing:
bootstrap(AppMock,[provide(Overlay,{
useClass:OverlayMock
})]);
Intheprecedingbootstrapcall,weprovidedadifferenttop-levelcomponentandproviderfortheOverlayservicethatwillreturnaninstanceoftheOverlayMockclass.Thisway,wecantesttheTooltipdirectiveignoringtheimplementationofOverlay.
ExploringDIwithcomponentsSincecomponentsaregenerallydirectiveswithviews,everythingwe’veseensofarregardinghowtheDImechanismworkswithdirectivesisvalidforcomponentsaswell.However,becauseoftheextrafeaturesthatthecomponentsprovide,we’reallowedtohavefurthercontrolovertheirproviders.
Aswesaid,theinjectorassociatedwitheachcomponentwillbemarkedasahostinjector.There’saparameterdecoratorcalled@Host,whichallowsustoretrieveagivendependencyfromanyinjectoruntilitreachestheclosesthostinjector.Thismeansthatbyusingthe@Hostdecoratorinadirective,wecandeclarethatwewanttoretrievethegivendependencyfromthecurrentinjectororanyparentinjectoruntilwereachtheinjectoroftheclosestparentcomponent.
TheviewProviderspropertyaddedtothe@Componentdecoratorisinchargeofachievingevenmorecontrol.
viewProvidersversusprovidersLet’stakealookatanexampleofacomponentcalledMarkdownPanel.Thiscomponentwillbeusedinthefollowingway:
<markdown-panel>
<panel-title>#Title</pane-title>
<panel-content>
#Contentofthepanel
*Firstpoint
*Secondpoint
</panel-content>
</markdown-panel>
ThecontentofeachsectionofthepanelwillbetranslatedfromthemarkdowntotheHTML.WecandelegatethisfunctionalitytoaservicecalledMarkdown:
import*asmarkdownfrom'markdown';
classMarkdown{
toHTML(md){
returnmarkdown.toHTML(md);
}
}
TheMarkdownservicewrapsthemarkdownmoduleinordertomakeitinjectablethroughtheDImechanism.
Nowlet’simplementMarkdownPanel.
Inthefollowingsnippet,wecanfindalltheimportantdetailsfromthecomponent’simplementation:
//ch5/ts/directives/app.ts
@Component({
selector:'markdown-panel',
viewProviders:[Markdown],
styles:[...],
template:`
<divclass="panel">
<divclass="panel-title">
<ng-contentselect="panel-title"></ng-content>
</div>
<divclass="panel-content">
<ng-contentselect="panel-content"></ng-content>
</div>
</div>`
})
classMarkdownPanel{
constructor(privateel:ElementRef,privatemd:Markdown){}
ngAfterContentInit(){
letel=this.el.nativeElement;
lettitle=el.querySelector('panel-title');
letcontent=el.querySelector('panel-content');
title.innerHTML=this.md.toHTML(title.innerHTML);
content.innerHTML=this.md.toHTML(content.innerHTML);
}
}
Weusedthemarkdown-panelselectorandsettheviewProvidersproperty.Inthiscase,there’sonlyasingleviewprovider:theonefortheMarkdownservice.Bysettingthisproperty,wedeclaredthatalltheprovidersdeclaredinitwillbeaccessiblefromthecomponentitselfandallofitsviewchildren.
Now,let’ssupposewehaveacomponentcalledMarkdownButtonandwewanttoaddittoourtemplateinthefollowingway:
<markdown-panel>
<panel-title>###Smalltitle</panel-title>
<panel-content>
Somecode
</panel-content>
<markdown-button>*Clicktotoggle*</markdown-button>
</markdown-panel>
TheMarkdownservicewillnotbeaccessiblebytheMarkdownButtonusedbelowthepanel-contentelement;however,it’llbeaccessibleifweusethebuttoninthe
component’stemplate:
@Component({
selector:'markdown-panel',
viewProviders:[Markdown],
directives:[MarkdownButton],
styles:[…],
template:`
<divclass="panel">
<markdown-button>*Clicktotoggle*</markdown-button>
<divclass="panel-title">
<ng-contentselect="panel-title"></ng-content>
</div>
<divclass="panel-content">
<ng-contentselect="panel-content"></ng-content>
</div>
</div>`
})
Ifweneedtheprovidertobevisibleinallthecontentandviewchildren,allweshoulddoischangethepropertynameoftheviewProviderspropertytoproviders.
Youcanfindthisexampleinthefileintheexamplesdirectoryatch5/ts/directives/app.ts.
UsingAngular’sDIwithES5WearealreadyproficientinusingthedependencyinjectionofAngular2withTypeScript!Asweknow,wearenotlimitedtoTypeScriptforthedevelopmentofAngular2applications;wecanalsouseES5,ES2015,andES2016(aswellasDart,butthatisoutofthescopeofthisbook).
Sofar,wedeclaredthedependenciesofthedifferentclassesintheirconstructorusingstandardTypeScripttypeannotations.Allsuchclassesaresupposedtobedecoratedwiththe@Injectabledecorator.Unfortunately,someoftheotherlanguagessupportedbyAngular2missafewofthesefeatures.Inthefollowingtable,wecanseethatES5doesn’tsupporttypeannotations,classes,anddecorators:
ES5 ES2015 ES2016
Classes No Yes Yes
Decorators No No Yes(noparameterdecorators)
Typeannotations No No No
Inthiscase,howwecantakeadvantageoftheDImechanismintheselanguages?Angular2providesaninternalJavaScriptDomainSpecificLanguage(DSL),whichallowsustotakeadvantageoftheentirefunctionalityoftheframeworkusingES5.
Now,let’stranslatetheMarkdownPanelexamplewetookalookatintheprevioussectionfromTypeScripttoES5.First,let’sstartwiththeMarkdownservice:
//ch5/es5/simple-example/app.js
varMarkdown=ng.core.Class({
constructor:function(){},
toHTML:function(md){
returnmarkdown.toHTML(md);
}
});
WedefinedavariablecalledMarkdownandsetitsvaluetothereturnedresultfromtheinvocationofng.core.Class.ThisconstructallowsustoemulateES2015classesusingES5.Theargumentoftheng.core.Classmethodisanobjectliteral,whichmusthavethedefinitionofaconstructorfunction.Asaresult,ng.core.ClasswillreturnaJavaScriptconstructorfunctionwiththebodyofconstructorfromtheobjectliteral.Alltheothermethodsdefinedwithintheboundariesofthepassedparameterwillbeaddedtothefunction’sprototype.
Oneproblemissolved:wecannowemulateclassesinES5;therearetwomoreproblemsleft!
Now,let’stakealookathowwecandefinetheMarkdownPanelcomponent:
//ch5/es5/simple-example/app.js
varMarkdownPanel=ng.core.Component({
selector:'markdown-panel',
viewProviders:[Markdown],
styles:[...],
template:'...'
})
.Class({
constructor:[Markdown,ng.core.ElementRef,function(md,el){
this.md=md;
this.el=el;
}],
ngAfterContentInit:function(){
…
}
});
FromChapter4,GettingStartedwithAngular2ComponentsandDirectives,wearealreadyfamiliarwiththeES5syntaxusedtodefinecomponents.Now,let’stakealookattheconstructorfunctionofMarkdownPanelinordertomakesurehowwecandeclarethedependenciesofourcomponentsandevenclassesingeneral.
Fromtheprecedingsnippet,wecannotethatthevalueoftheconstructorisnotafunctionthistime,butanarrayinstead.ThismightseemfamiliartoyoufromAngularJS1.x,whereweareabletodeclarethedependenciesofthegivenservicebylistingtheirnames:
Module.service('UserMapper',
['User','$http',function(User,$http){
//…
}]);
AlthoughthesyntaxinAngular2issimilar,itbringsalotofimprovements.Forinstance,we’renolongerlimitedtousingstringsforthedependencies’tokens.
Now,let’ssupposewewanttomaketheMarkdownserviceanoptionaldependency.Inthiscase,wecanapproachthisbypassinganarrayofdecorators:
…
.Class({
constructor:[[ng.core.Optional(),Markdown],
ng.core.ElementRef,function(md,el){
this.md=md;
this.el=el;
}],
ngAfterContentInit:function(){
…
}
});
…
Thisway,bynestingarrays,wecanapplyasequenceofdecorators:[[ng.core.Optional(),ng.core.Self(),Markdown],...].Inthisexample,the@Optionaland@Selfdecoratorswilladdtheassociatedmetadatatotheclassinthespecifiedorder.
AlthoughusingES5makesourbuildsimplerandallowsustoskiptheintermediatestepof
transpilation,whichcanbetempting,Google’srecommendationistotakeadvantageofstatictypingusingTypeScript.Thisway,wehaveamuchclearersyntax,whichcarriesbettersemanticswithlesstypingandprovidesuswithgreattooling.
SummaryInthischapter,wecoveredtheDImechanismofAngular2.Webrieflydiscussedthepositivesofusingdependencyinjectioninourprojectsbyintroducingitinthecontextoftheframework.Thesecondstepinourjourneywashowtoinstantiateandconfigureinjectors;wealsoexplainedtheinjectors’hierarchyandthevisibilityoftheregisteredproviders.Inordertoenforceabetterseparationofconcerns,wementionedhowwecaninjectservicescarryingthebusinesslogicofourapplicationinourdirectivesandcomponents.ThelastpointwetookalookatwashowwecanusetheDImechanismwiththeES5syntax.
Inthenextchapter,we’llintroducethenewroutingmechanismoftheframework.We’llexplainhowwecanconfigurethecomponent-basedrouterandaddmultipleviewstoourapplication.Anotherimportanttopicwearegoingtocoveristhenewformmodule.Bybuildingasimpleapplication,wewilldemonstratehowwecancreateandmanageforms.
Chapter6.WorkingwiththeAngular2RouterandFormsBynow,we’realreadyfamiliarwiththecoreoftheframework.Weknowhowtodefinecomponentsanddirectivesinordertodeveloptheviewofourapplications.Wealsoknowhowtoencapsulatebusiness-relatedlogicintoservicesandwireeverythingtogetherwiththedependencyinjectionmechanismofAngular2.
Inthischapter,we’llexplainafewmoreconceptsthatwillhelpusbuildreal-lifeAngular2applications.Theyareasfollows:
Thecomponent-basedrouteroftheframework.UsingAngular2forms.Developingtemplate-drivenforms.Developingcustomformvalidators.
Let’sbegin!
Developingthe“Codersrepository”applicationIntheprocessofexplainingtheconceptsmentionedearlier,we’lldevelopasampleapplicationthatcontainsarepositoryofdevelopers.Beforewestartcoding,let’sexplainthestructureoftheapplication.
The“Codersrepository”willallowitsuserstoadddeveloperseitherbyfillingaformwithdetailsaboutthemorbyprovidingtheGitHubhandleforthedeveloperandimportinghisorherprofilefromGitHub.
NoteForthepurposeofthischapter,wewillstoreinformationonthedevelopersinmemory,whichmeansthatafterthepageisrefreshed,we’llloseallthestoredduringthesessiondata.
Theapplicationwillhavethefollowingviews:
Alistofallthedevelopers.Aviewthataddsorimportsnewdevelopers.Aviewthatshowsthegivendeveloper’sdetails.Thisviewhastwosubviews:
Basicdetails:ShowsthenameofthedeveloperandherorhisGitHubavatarifavailable.Advancedprofile:Showsallthedetailsknownforthedeveloper.
Theendresultofthehomepageoftheapplicationwilllookasfollows:
Fig.1
NoteInthischapter,wewillbuildonlyafewofthelistedviews.Therestoftheapplicationwill
beexplainedinChapter7,ExplainingPipesandCommunicatingwithRESTfulServices.
Eachdeveloperwillbeaninstanceofthefollowingclass:
//ch6/ts/multi-page-template-driven/developer.ts
exportclassDeveloper{
publicid:number;
publicgithubHandle:string;
publicavatarUrl:string;
publicrealName:string;
publicemail:string;
publictechnology:string;
publicpopular:boolean;
}
AllthedeveloperswillresidewithintheDeveloperCollectionclass:
//ch6/ts/multi-page-template-driven/developer_collection.ts
classDeveloperCollection{
privatedevelopers:Developer[]=[];
getUserByGitHubHandle(username:string){
returnthis.developers
.filter(u=>u.githubHandle===username)
.pop();
}
getUserById(id:number){
returnthis.developers
.filter(u=>u.id===id)
.pop();
}
addDeveloper(dev:Developer){
this.developers.push(dev);
}
getAll(){
returnthis.developers;
}
}
Theclassesmentionedhereencapsulatequiteasimplelogicanddon’thaveanythingAngular2-specific,sowewon’tgetintoanydetails.
Now,let’scontinuewiththeimplementationbyexploringthenewrouter.
ExploringtheAngular2routerAswealreadyknow,inordertobootstrapanyAngular2application,weneedtodeveloparootcomponent.The“Codersrepository”applicationisnotanydifferent;theonlyadditioninthisspecificcaseisthatwewillhavemultiplepagesthatneedtobeconnectedtogetherwiththeAngular2router.
Let’sstartwiththeimportsrequiredfortherouter’sconfigurationanddefinetherootcomponentrightafterthis:
//ch6/ts/step-0/app.ts
import{
ROUTER_DIRECTIVES,
ROUTER_PROVIDERS,
Route,
Redirect,
RouteConfig,
LocationStrategy,
HashLocationStrategy
}from'angular2/router';
Intheprecedingsnippet,weimportedacoupleofthingsdirectlyfromtheAngular2routermodule,whichisexternalizedoutsidetheframework’score.
WithROUTER_DIRECTIVES,therouterprovidesasetofcommonlyuseddirectivesthatwecanaddtothelistofusedonesbytherootcomponent.Thisway,wewillbeabletousetheminthetemplateslater.
TheimportROUTE_PROVIDERScontainsasetofrouter-relatedproviders,suchasoneforinjectingtheRouteParamstokenintothecomponents’constructors.
TheRouteParamstokenprovidesanaccesstoparametersfromtheroute’sURLinordertoparametrizethelogicassociatedwithagivenpage.We’lldemonstrateatypicalusecaseofthisproviderlater.
TheimportLocationStrategyclassisanabstractclassthatdefinesthecommonlogicbetweenHashLocationStrategy(usedforhash-basedrouting)andPathLocationStrategy(usedforHTML5-basedroutingbytakingadvantageofthehistoryAPI).
NoteHashLocationStrategydoesnotsupportserver-siderendering.Thisisduetothefactthatthehashofthepagedoesnotgetsenttotheserver,soitcannotfindoutthecomponentassociatedwiththegivenpage.AllmodernbrowsersexceptIE9supporttheHTML5historyAPI.Youcanfindmoreaboutserver-siderenderinginthelastchapterofthebook.
Thelastimportswedidn’ttakealookatareRouteConfig,whichisadecoratorthatallowsustodefinetheroutesassociatedwithagivencomponent;andRouteandRedirect,whichrespectivelyallowustodefinetheindividualroutesandredirects.WithRouteConfig,wecandefineahierarchyofroutes,whichmeansthattherouterofAngular
2supportsnestedroutingoutoftheboxunlikeitspredecessorinAngularJS1.x.
DefiningtherootcomponentandbootstrappingtheapplicationNow,let’sdefinearootcomponentandconfiguretheapplication’sinitialbootstrap:
//ch6/ts/step-0/app.ts
@Component({
selector:'app',
template:`…`,
providers:[DeveloperCollection],
directives:[ROUTER_DIRECTIVES]
})
@RouteConfig([…])
classApp{}
bootstrap(…);
Intheprecedingsnippet,youcannoticeasyntaxwe’realreadyfamiliarwithfromChapter4,GettingStartedwithAngular2ComponentsandDirectivesandChapter5,DependencyInjectioninAngular2.Wedefinedacomponentwithanappselector,templatethatwe’regoingtotakealookatlater,andsetsofprovidersanddirectives.
TheAppcomponentusesasingleprovidercalledDeveloperCollection.Thisistheclassthatcontainsallthedevelopersstoredbytheapplication.YoucannoticethatweaddedROUTER_DIRECTIVES;itcontainsanarrayofallthedirectivesdefinedwithintheAngular’srouter.Someofthedirectiveswithinthisarrayallowustolinktotheotherroutesdefinedwithinthe@RouteConfigdecorator(therouterLinkdirective)anddeclaretheplacewherethecomponentsassociatedwiththedifferentroutesshouldberendered(router-outlet).We’llexplainhowwecanusethemlaterinthissection.
Nowlet’stakealookatthecallofthebootstrapfunction:
bootstrap(App,[
ROUTER_PROVIDERS,
provide(LocationStrategy,{useClass:HashLocationStrategy})
)]);
Asthefirstargumentofbootstrap,wepasstherootcomponentoftheapplicationasusual.Thesecondargumentisalistofprovidersthatwillbeaccessiblebytheentireapplication.Tothesetofproviders,weaddROUTER_PROVIDERSandwealsoconfiguretheproviderfortheLocationStrategytoken.ThedefaultLocationStrategytoken,whichAngular2uses,isPathLocationStrategy(thatis,theHTML5-basedone).However,inthiscase,wearegoingtousethehash-basedone.
Thetwobiggestadvantagesofthedefaultlocationstrategyarethatitissupportedbytheserver-renderingmoduleofAngular2,andtheapplication’sURLlooksmorenaturaltotheenduser(there’sno#used).Ontheotherhand,incaseweusePathLocationStrategy,wemayneedtoconfigureourapplicationserver,inordertohandletheroutesproperly.
UsingPathLocationStrategyIfwewanttousePathLocationStrategy,wemayneedtoprovideAPP_BASE_HREF.Forinstance,inourcase,thebootstrapconfigurationshouldlookasfollows:
import{APP_BASE_HREF}from'angular2/router';
//...
bootstrap(App,[
ROUTER_PROVIDERS,
//Thefollowinglineisoptional,sinceit's
//thedefaultvaluefortheLocationStrategytoken
provide(LocationStrategy,{useClass:PathLocationStrategy}),
provide(APP_BASE_HREF,{
useValue:'/dist/dev/ch6/ts/multi-page-template-driven/'
}
)]);
Bydefault,thevalueassociatedwiththeAPP_BASE_HREFtokenis/;itrepresentsthebasepathnameinsideoftheapplication.Forinstance,inourcase,the“Codersrepository”willbelocatedunderthe/ch6/ts/multi-page-template-driven/directory(thatis,http://localhost:5555/dist/dev/ch6/ts/multi-page-template-driven/).
Configuringrouteswith@RouteConfigAsthenextstep,let’stakealookattheroute’sdeclarationplacedinthe@RouteConfigdecorator:
//ch6/ts/step-0/app.ts
@Component(…)
@RouteConfig([
newRoute({component:Home,name:'Home',path:'/'}),
newRoute({
component:AddDeveloper,
name:'AddDeveloper',
path:'/dev-add'
}),
//…
newRedirect({
path:'/add-dev',
redirectTo:['/dev-add']
})
])
classApp{}
Astheprecedingsnippetshows,the@RouteConfigdecoratoracceptsanarrayofroutesasanargument.Intheexample,wedefinedtwotypesofroutes:usingtheclassesRouteandRedirect.Theyareusedrespectivelytodefinetheroutesandredirectsintheapplication.
Eachroutemustdefinethefollowingproperties:
component:Thecomponentassociatedwiththegivenroute.name:Thenameoftherouteusedforreferencingitinthetemplates.path:Thepathtobeusedfortheroute.Itwillbevisibleinthebrowser’slocationbar.
NoteTheRouteclassalsosupportsadatapropertywhosevaluecanbeinjectedintheconstructorofitsassociatedcomponentbyusingtheRouteDatatoken.Asampleusecaseofthedatapropertycouldbeifwewanttoinjectdifferentconfigurationobjectsbasedonthetypeoftheparentcomponentthatcontainsthe@RouteConfigdeclaration.
Ontheotherhand,theredirectcontainsonlytwoproperties:
path:Thepathtobeusedfortheredirection.redirectTo:Thepaththeuserisredirectedto.
Inthepreviousexample,wedeclaredthatwewantthepageopenedbytheuserwiththepath/add-devtoberedirectedto['/dev-add'].
Now,inordertomakeeverythingwork,weneedtodefinetheAddDeveloperandHomecomponents,[email protected],we’regoingtoprovideabasicimplementationthatwe’llincrementallyextendovertimealongthechapter.Inch6/ts/step-0,createafilecalledhome.tsandenterthefollowingcontent:
import{Component}from'angular2/core';
@Component({
selector:'home',
template:`Home`
})
exportclassHome{}
DonotforgettoimporttheHomecomponentinapp.ts.Now,openthefilecalledadd_developer.tsandenterthefollowingcontentinit:
import{Component}from'angular2/core';
@Component({
selector:'dev-add',
template:`Adddeveloper`
})
exportclassAddDeveloper{}
UsingrouterLinkandrouter-outletWehavetheroute’sdeclarationandallthecomponentsassociatedwiththeindividualroutes.TheonlythingleftistodefinethetemplateoftherootAppcomponentinordertolinkeverythingtogether.
Addthefollowingcontenttothetemplatepropertyinsidethe@Componentdecoratorinch6/ts/step-0/app.ts:
@Component({
//…
template:`
<navclass="navbarnavbar-default">
<ulclass="navnavbar-nav">
<li><a[routerLink]="['/Home']">Home</a></li>
<li><a[routerLink]="['/AddDeveloper']">Adddeveloper</a></li>
</ul>
</nav>
<router-outlet></router-outlet>
`,
//…
})
InthetemplateabovetherearetwoAngular2-specificdirectives:
routerLink:Thisallowsustoaddalinktoaspecificroute.router-outlet:Thisdefinesthecontainerwherethecomponentsassociatedwiththecurrentlyselectedrouteneedtoberendered.
Let’stakealookattherouterLinkdirective.Asvalueitacceptsanarrayofroutenamesandparameters.Inourcaseweprovideonlyasingleroutenameprefixedwithslash(sincethisrouteisonrootlevel).NoticethattheroutenameusedbyrouterLinkisdeclaredbythenamepropertyoftheroutedeclarationinside@RouteConfig.Laterinthischapterwe’llseehowwecanlinktonestedroutesandpassrouteparameters.
ThisdirectiveallowsustodeclarelinksindependentlyfromLocationStrategythatwehaveconfigured.Forinstance,imagineweareusingHashLocationStrategy;thismeansthatweneedtoprefixalltheroutesinourtemplateswith#.IncaseweswitchtoPathLocationStrategy,we’llneedtoremoveallthehashprefixes.AnotherhugebenefitofrouterLinkisthatitusestheHTML5historypushAPItransparentlytous,whichsavesusfromalotofboilerplates.
Thenextdirectivefromtheprevioustemplatethatisnewtousisrouter-outlet.Ithassimilarresponsibilitytotheng-viewdirectiveinAngularJS1.x.Basically,theybothhavethesamerole:topointoutwherethetargetcomponentshouldberendered.Thismeansthataccordingtothedefinition,whentheusernavigatesto/,theHomecomponentwillberenderedatthepositionpointedoutbyrouter-outlet,samefortheAddDevelopercomponentoncetheusernavigatesto/dev-add.
Nowwehavethesetworoutesupandrunning!Openhttp://localhost:5555/dist/dev/ch6/ts/step-0/andyoushouldseethefollowing
screenshot:
Fig.2
Ifyoudon’t,justtakealookatch6/ts/step-1thatcontainstheendresult.
Lazy-loadingwithAsyncRouteAngularJS1.xmodulesallowustogrouptogetherlogicallyrelatedunitsintheapplication.However,bydefault,theyneedtobeavailableduringtheinitialapplication’sbootstrapanddonotallowdeferredloading.Thisrequiresdownloadingtheentirecodebaseoftheapplicationduringtheinitialpageloadthat,incaseoflargesingle-pageapps,canbeanunacceptableperformancehit.
Inaperfectscenario,wewouldwanttoloadonlythecodeassociatedwiththepagetheuseriscurrentlyviewing,ortoprefetchbundledmodulesbasedonheuristicsrelatedtotheuser’sbehavior,whichisoutofthescopeofthisbook.Forinstance,opentheapplicationfromthefirststepofourexample:http://localhost:5555/dist/dev/ch6/ts/step-1/.Oncetheuserisat/,weonlyneedtheHomecomponenttobeavailable,andonceheorshenavigatesto/dev-add,wewanttoloadtheAddDevelopercomponent.
Let’sinspectwhatisactuallygoingoninChromeDevTools:
Fig.3
Wecannoticethatduringtheinitialpageload,wedownloadedthecomponentsassociatedwithalltheroutes,evenAddDeveloperthatisnotrequired.Thishappensbecauseinapp.ts,weexplicitlyrequireboththeHomeandtheAddDevelopercomponentsandusetheminthe@RouteConfigdeclaration.
Inthisspecificcase,loadingboththecomponentsmaynotseemlikeabigproblem,becauseatthisstep,theyareprettysimpleanddonothaveanydependencies.However,inreal-lifeapplications,theywillhaveimportsofotherdirectives,components,pipes,services,oreventhird-partylibraries.Onceanyofthecomponentsisrequired,itsentire
dependencygraphwillbedownloaded,evenifthecomponentisnotneededatthatpoint.
TherouterofAngular2comeswithasolutiontothisproblem.AllweneedtodoisimporttheAsyncRouteclassfromtheangular2/routermoduleanduseitinside@RouteConfiginsteadofusingRoute:
//ch6/ts/step-1-async/app.ts
import{AsyncRoute}from'angular2/router';
@Component(…)
@RouteConfig([
newAsyncRoute({
loader:()=>
System.import('./home')
.then(m=>m.Home),
name:'Home',
path:'/'
}),
newAsyncRoute({
loader:()=>
System.import('./add_developer')
.then(m=>m.AddDeveloper),
name:'AddDeveloper',
path:'/dev-add'
}),
newRedirect({path:'/add-dev',redirectTo:['/dev-add']})
])
classApp{}
TheconstructoroftheAsyncRouteclassacceptsasanargumentanobjectwiththefollowingproperties:
loader:Afunctionthatreturnsapromisethatneedstoberesolvedwiththecomponentassociatedwiththegivenroute.name:Thenameoftheroutethatcanbeusedtorefertoitinthetemplates(usually,insideoftherouterLinkdirective).path:Thepathoftheroute.
Oncetheusernavigatestoaroutethatmatchesanyoftheasyncroutes’definitionsinthe@RouteConfigdecorator,itsassociatedloaderwillbeinvoked.Whenthepromisereturnedbytheloaderisresolvedwithavalueofthetargetcomponent,thecomponentwillbecachedandrendered.Nexttimetheusernavigatestothesameroute,thecachedcomponentwillbeused,sotheroutingmodulewon’tdownloadthesamecomponenttwice.
NoteNoticethattheprecedingexampleusesSystem,however,Angular’sAsyncRouteimplementationisnotcoupledtoanyparticularmoduleloader.Thesameresultcouldbeachieved,forinstance,withrequire.js.
UsingAngular2formsNowlet’scontinuewiththeimplementationoftheapplication.Forthenextstep,we’llworkontheAddDeveloperandHomecomponents.Youcancontinueyourimplementationbyextendingwhatyoucurrentlyhaveinch6/ts/step-0,orifyouhaven’treachedstep1yet,youcankeepworkingonthefilesinch6/ts/step-1.
Angular2offerstwowaystodevelopformswithvalidation:
Atemplate-drivenapproach:ProvidesadeclarativeAPIwherewedeclarethevalidationsintothetemplateofthecomponent.Amodel-drivenapproach:ProvidesanimperativeAPIwithFormBuilder.
Inthenextchapter,we’llexploreboth.Let’sstartwiththetemplate-drivenapproach.
Developingtemplate-drivenformsFormsareessentialforeachCRUD(CreateRetrieveUpdateandDelete)application.Inourcase,wewanttobuildaformforenteringthedetailsofthedeveloperswewanttostore.
Bytheendofthissection,we’llhaveaformthatallowsustoentertherealnameofagivendeveloper,toaddhisorherpreferredtechnology,enterane-mail,anddeclarewhethersheorheispopularinthecommunityornotyet.Theendresultwilllookasfollows:
Fig.4
Addthefollowingimportstoadd_developer.ts:
import{
FORM_DIRECTIVES,
FORM_PROVIDERS
}from'angular2/common;
ThenextthingweneedtodoisaddFORM_DIRECTIVEStothelistofdirectivesusedbytheAddDevelopercomponent.TheFORM_DIRECTIVESdirectivescontainsasetofpredefineddirectivesformanagingAngular2forms,suchastheformandngModeldirectives.
TheFORM_PROVIDERSisanarraywithapredefinedsetofprovidersthatwecanuseforinjectingthevaluesassociatedwiththeirtokensintheclassesofourapplication.
NowupdatetheAddDeveloperimplementationtothefollowing:
@Component({
selector:'dev-add',
templateUrl:'./add_developer.html',
styles:[…],
directives:[FORM_DIRECTIVES],
providers:[FORM_PROVIDERS]
})
exportclassAddDeveloper{
developer=newDeveloper();
errorMessage:string;
successMessage:string;
submitted=false;
technologies:string[]=[
'JavaScript',
'C',
'C#',
'Clojure'
];
constructor(privatedevelopers:DeveloperCollection){}
addDeveloper(){}
}
Thedeveloperpropertycontainstheinformationassociatedwiththecurrentdeveloperthatwe’readdingwiththeform.Thelasttwoproperties,errorMessageandsuccessMessage,aregoingtobeusedrespectivelyfordisplayingthecurrentform’serrororsuccessmessagesoncethedeveloperhasbeensuccessfullyaddedtothedeveloperscollection,oranerrorhasoccurred.
Diggingintothetemplate-drivenform’smarkupAsthenextstep,let’screatethetemplateoftheAddDevelopercomponent(step-1/add_developer.html).Addthefollowingcontenttothefile:
<span*ngIf="errorMessage"
class="alertalert-danger">{{errorMessage}}</span>
<span*ngIf="successMessage"
class="alertalert-success">{{successMessage}}</span>
Thesetwoelementsareintendedtodisplaytheerrorandsuccessmessageswhenaddinganewdeveloper.TheyaregoingtobevisiblewhenerrorMessageandsuccessMessagerespectivelyhavenon-falsyvalues(thatis,somethingdifferentfromtheemptystring,false,undefined,0,NaN,ornull).
Nowlet’sdeveloptheactualform:
<form#f="ngForm"(ngSubmit)="addDeveloper()"
class="formcol-md-4"[hidden]="submitted">
<divclass="form-group">
<labelclass="control-label"
for="realNameInput">Realname</label>
<div>
<inputid="realNameInput"class="form-control"
type="text"ngControl="realName"required
[(ngModel)]="developer.realName">
</div>
</div>
<buttonclass="btnbtn-default"
type="submit"[disabled]="!f.form.valid">Add</button>
<!--MORECODETOBEADDED-->
</form>
WedeclareanewformusingtheHTMLformtag.OnceAngular2findssuchtagsinatemplatewithanincludedformdirectiveintheparentcomponent,itwillautomaticallyenhanceitsfunctionalityinordertobeusedasanAngularform.OncetheformisprocessedbyAngular,wecanapplyformvalidationanddata-bindings.Afterthis,using#f="ngForm",wewilldefinealocalvariableforthetemplatecalledf,whichallowsustoreferencetothecurrentform.Thelastthingleftfromtheformelementisthesubmiteventhandler.Weuseasyntaxthatwe’realreadyfamiliarwith(ngSubmit)="expr",whereinthiscase,thevalueoftheexpressionisthecalloftheaddDevelopermethodattachedtothecomponent’scontroller.
Now,let’stakealookatthedivelementwithclassnamecontrol-group.
NoteNotethatthisisnotanAngular-specificclass;itisaCSSclassdefinedbyBootstrapthatweuseinordertoprovideabetterlookandfeeltotheform.
Insideofit,wecanfindalabelelementthatdoesn’thaveanyAngular-specificmarkupandaninputelementthatallowsustosettherealnameofthecurrentdeveloper.WesetthecontroltobeofatypetextanddeclareitsidentifiertoequalrealNameInput.The
requiredattributeisdefinedbytheHTML5specificationandisusedforvalidation.Byusingitontheelement,wedeclarethatitisrequiredforthiselementtohaveavalue.AlthoughthisattributeisnotAngular-specificusingthengControlattribute,Angularwillextendthesemanticsoftherequiredattributebyincludingvalidationbehavior.ThisbehaviorincludessettingspecificCSSclassesonthecontrolwhenitsstatuschangesandmanagingitsstatethattheframeworkkeepsinternally.
ThengControldirectiveisaselectoroftheNgControlNamedirective.Itenhancesthebehavioroftheformcontrolsbyrunningvalidationoverthemforthechangeoftheirvalues,andapplyingspecificclassesduringthecontrols’lifecycle.YoumightbefamiliarwiththisfromAngularJS1.xwheretheformcontrolsaredecoratedwiththeng-pristine,ng-invalid,andng-validclasses,andsoon,inspecificphasesoftheirlifecycle.
ThefollowingtablesummarizestheCSSclassesthattheframeworkaddstotheformcontrolsduringtheirlifecycle:
Classes Description
ng-untouched Thecontrolhasn’tbeenvisited
ng-touched Thecontrolhasbeenvisited
ng-pristine Thecontrol’svaluehasn’tbeenchanged
ng-dirty Thecontrol’svaluehasbeenchanged
ng-valid Allthevalidatorsattachedtothecontrolhavereturnedtrue
ng-invalid Anyofthevalidatorsattachedtothecontrolhasafalsevalue
Accordingtothistable,wecandefinethatwewantalltheinputcontrolswithinvalidvaluetohavearedborderinthefollowingway:
input.ng-dirty.ng-invalid{
border:1pxsolidred;
}
TheexactsemanticsbehindtheprecedingCSSinthecontextofAngular2istousearedborderforalltheinputelementswhosevalueshavebeenchangedandareinvalidaccordingtothevalidatorsattachedtothem.
Now,let’sexplorehowwecanattachdifferentvalidationbehaviortoourcontrols.
Usingthebuilt-informvalidatorsWealreadysawthatwecanaltervalidationbehaviortoanycontrolbyusingtherequiredattribute.Angular2providestwomorebuilt-invalidators,asfollows:
minlength:Allowsustospecifytheminimumlengthofthevaluethatagivencontrolshouldhave.maxlength:Allowsustospecifythemaximumlengthofthevaluethatagivencontrolshouldhave.
ThesevalidatorsaredefinedwithAngular2directivesandcanbeusedinthefollowingway:
<inputid="realNameInput"class="form-control"
type="text"ngControl="realName"
minlength="2"
maxlength="30">
Thisway,wespecifythatwewantthevalueoftheinputtobebetween2and30characters.
DefiningcustomcontrolvalidatorsAnotherdatapropertydefinedintheDeveloperclassistheemailfield.Let’saddaninputfieldforthisproperty.Abovethebuttonintheprecedingform,addthefollowingmarkup:
<divclass="form-group">
<labelclass="control-label"for="emailInput">Email</label>
<div>
<inputid="emailInput"
class="form-control"
type="text"ngControl="email"
[(ngModel)]="developer.email"/>
</div>
</div>
Wecanthinkofthe[(ngModel)]attributeasanalternativetotheng-modeldirectivefromAngularJS1.x.WewillexplainitindetailintheTwo-waydatabindingwithAngular2section.
AlthoughAngular2providesasetofpredefinedvalidators,theyarenotenoughforallthevariousformatsourdatacanlivein.Sometimes,we’llneedcustomvalidationlogicforourapplication-specificdata.Forinstance,inthiscase,wewanttodefineane-mailvalidator.Atypicalregularexpression,whichworksingeneralcases(butdoesnotcovertheentirespecificationthatdefinestheformatofthee-mailaddresses),looksasfollows:/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/.
Inch6/ts/step-1/add_developer.ts,defineafunctionthatacceptsaninstanceofAngular2controlasanargumentandreturnsnullifthecontrol’svalueisemptyormatchestheregularexpressionmentionedearlier,and{'invalidEmail':true}otherwise:
functionvalidateEmail(emailControl){
if(!emailControl.value||
/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-
9-.]+$/.test(emailControl.value)){
returnnull;
}else{
return{'invalidEmail':true};
}
}
Now,fromthemodulesangular2/commonandangular2/coreimportNG_VALIDATORSandDirective,andwrapthisvalidationfunctionwithinthefollowingdirective:
@Directive({
selector:'[email-input]',
providers:[provide(NG_VALIDATORS,{
useValue:validateEmail,multi:true
})]
})
classEmailValidator{}
Intheprecedingcode,wedefinedasinglemultiproviderforthetokenNG_VALIDATORS.
Onceweinjectthevalueassociatedwiththistoken,we’llgetanarraywithallthevalidatorsattachedtothegivencontrol(forreference,takealookatthesectionformultiprovidersinChapter5,DependencyInjectioninAngular2).
Theonlytwostepsleftinordertomakeourcustomvalidationworkaretofirstaddtheemail-inputattributetothee-mailcontrol:
<inputid="emailInput"
class="form-control"
email-input
type="text"ngControl="email"
[(ngModel)]="developer.email"/>
Next,toaddthedirectivetothelistusedbythecomponentAddDeveloperdirectives:
@Component({
selector:'dev-add',
templateUrl:'./add_developer.html',
styles:[`
input.ng-touched.ng-invalid{
border:1pxsolidred;
}
`],
directives:[FORM_DIRECTIVES,EmailValidator],
providers:[FORM_PROVIDERS]
})
classAddDeveloper{…}
NoteWe’reusinganexternaltemplatefortheAddDevelopercontrol.There’snoultimateanswertowhetheragiventemplateshouldbeexternalizedorinlinedwithinthecomponentwithtemplateUrlortemplate,respectively.Thebestpracticestatesthatweshouldinlinetheshorttemplatesandexternalizethelongerones,butthere’snospecificdefinitionastowhichtemplatesareconsideredshortandwhicharelong.Thedecisiononwhetherthetemplateshouldbeusedinlineorputintoanexternalfiledependsonthedeveloper’spersonalpreferencesorcommonconventionswithintheorganization.
UsingselectinputswithAngularAsthenextstep,weshouldallowtheuseroftheapplicationtoenterthetechnologyintowhichtheinputdeveloperhasthemostproficiency.Wecandefinealistoftechnologiesandshowthemintheformasaselectinput.
IntheAddDeveloperclass,addthetechnologiesproperty:
classAddDeveloper{
…
technologies:string[]=[
'JavaScript',
'C',
'C#',
'Clojure'
];
…
}
Nowinthetemplate,justabovethesubmitbutton,addthefollowingmarkup:
<divclass="form-group">
<labelclass="control-label"
for="technologyInput">Technology</label>
<div>
<selectclass="form-control"
ngControl="technology"required
[(ngModel)]="developer.technology">
<option*ngFor="#toftechnologies"
[value]="t">{{t}}</option>
</select>
</div>
</div>
Justlikefortheinputelementswedeclaredearlier,Angular2willaddthesameclassesdependingonthestateoftheselectinput.Inordertoshowredborderaroundtheselectelementwhenitsvalueisinvalid,weneedtoaltertheCSSrules:
@Component({
…
styles:[
`input.ng-touched.ng-invalid,
select.ng-touched.ng-invalid{
border:1pxsolidred;
}`
],
…
})
classAddDeveloper{…}
NoteNoticethatinliningallthestylesinourcomponents’declarationcouldbeabadpractice,becausethisway,theywon’tbereusable.Whatwecandoisextractallthecommonstylesacrossourcomponentsintoseparatefiles.The@Componentdecoratorhasapropertycalled
styleUrlsoftypearraywherewecanaddareferencetotheextractedstylesusedbythegivencomponent.Thisway,wecaninlineonlythecomponent-specificstylesifrequired.
Rightafterthis,wewilldeclarethenameofthecontroltobeequalto“technology”usingngControl="technology".Byusingtherequiredattribute,wewilldeclarethattheuseroftheapplicationmustspecifythetechnologyintowhichthecurrentdeveloperisproficient.Let’sskipthe[(ngModel)]attributeforthelasttimeandseehowwecandefinetheselectelement’soptions.
Insidetheselectelement,wewilldefinethedifferentoptionsusing:
<option*ngFor="#toftechnologies"
[value]="t">{{t}}</option>
Thisisasyntaxwe’realreadyfamiliarwith.WewillsimplyiterateoverallthetechnologiesdefinedwithintheAddDeveloperclass,andforeachtechnology,wewillshowanoptionelementwithavalueofthetechnologyname.
UsingtheNgFormdirectiveWealreadymentionedthattheformdirectiveenhancestheHTML5form’sbehaviorbyaddingsomeadditionalAngular2-specificlogic.Now,let’stakeastepbackandtakealookattheformthatsurroundstheinputelements:
<form#f="ngForm"(ngSubmit)="addDeveloper()"
class="formcol-md-4"[hidden]="submitted">
…
</form>
Intheprecedingsnippet,wedefinedanewidentifiercalledf,whichreferencestotheform.Wecanthinkoftheformasacompositionofcontrols;wecanaccesstheindividualcontrolsthroughtheform’scontrolsproperty.Ontopofthis,theformhasthetouched,untouched,pristine,dirty,invalid,andvalidproperties,whichdependontheindividualcontrolsdefinedwithintheform.Forexample,ifnoneofthecontrolswithintheformhasbeentouched,thentheformitselfisgoingtobewiththestatusuntouched.However,ifanyofthecontrolsintheformhasbeentouchedatleastonce,theformwillbewiththestatustouchedaswell.Similarlytheformwillbevalidonlyifallitscontrolsarevalid.
Inordertoillustratetheusageoftheformelement,let’sdefineacomponentwiththeselectorcontrol-errors,whichshowsthecurrenterrorsforagivencontrol.Wecanuseitinthefollowingway:
<labelclass="control-label"for="realNameInput">Realname</label>
<div>
<inputid="realNameInput"class="form-control"type="text"
ngControl="realName"[(ngModel)]="developer.realName"
requiredmaxlength="50">
<control-errorscontrol="realName"
[errors]="{
'required':'Realnameisrequired',
'maxlength':'Themaximumlengthoftherealnameis50characters'
}"
/>
</div>
Noticethatwe’vealsoaddedthemaxlengthvalidatortotherealNamecontrol.
Thecontrol-errorselementhasthefollowingproperties:
control:Declaresthenameofthecontrolwewanttoshowerrorsfor.errors:Createsamappingbetweencontrolerrorandanerrormessage.
Nowaddthefollowingimportsinadd_developer.ts:
import{NgControl,NgForm}from'angular2/common';
import{Host}from'angular2/core';
Intheseimports,theNgControlclassistheabstractclassthatrepresentstheindividualformcomponents,NgFormrepresentstheAngularforms,andHostisaparameterdecoratorrelatedtothedependencyinjectionmechanism,whichwealreadycoveredinChapter5,DependencyInjectioninAngular2.
Hereisapartofthecomponent’sdefinition:
@Component({
template:'<div>{{currentError}}</div>',
selector:'control-errors',
inputs:['control','errors']
})
classControlErrors{
errors:Object;
control:string;
constructor(@Host()privateformDir:NgForm){}
getcurrentError(){…}
}
TheControlErrorscomponentdefinestwoinputs:control—thenameofthecontroldeclaredwiththengControldirective(thevalueofthengControlattribute)—anderrors—themappingbetweenanerrorandanerrormessage.Theycanbespecifiedrespectivelybythecontrolandtheerrorsattributesofthecontrol-errorselement.
Forinstance,ifwehavecontrol:
<inputtype="text"ngControl="foobar"required/>
Wecandeclareitsassociatedcontrol-errorscomponentbyusingthefollowing:
<control-errorscontrol="foobar"
[errors]="{
'required':'Thevalueoffoobarisrequired'
}"></control-errors>
InsideofthecurrentErrorgetter,intheprecedingsnippet,weneedtodothefollowingtwothings:
Findareferencetothecomponentdeclaredwiththecontrolattribute.Returntheerrormessageassociatedwithanyoftheerrorsthatmakethecurrentcontrolinvalid.
Hereisasnippetthatimplementsthisbehavior:
@Component(…)
classControlErrors{
…
getcurrentError(){
letcontrol=this.formDir.controls[this.control];
leterrorsMessages=[];
if(control&&control.touched){
errorsMessages=Object.keys(this.errors)
.map(k=>control.hasError(k)?this.errors[k]:null)
.filter(error=>!!error);
}
returnerrorsMessages.pop();
}
}
InthefirstlineoftheimplementationofcurrentError,wegetthetargetcontrolbyusingthecontrolspropertyoftheinjectedform.Itisofthetype{[key:string]:
AbstractControl},wherethekeyisthenameofthecontrolwe’vedeclaredwiththengControldirective.Oncewehaveareferencetotheinstanceofthetargetcontrol,wecancheckwhetheritsstatusistouched(thatis,whetherithasbeenfocused),andifitis,wecanloopoveralltheerrorswithintheerrorspropertyoftheinstanceofControlError.Themapfunctionwillreturnanarraywitheitheranerrormessageoranullvalue.Theonlythingleftistofilterallthenullvaluesandgetonlytheerrormessages.Oncewegettheerrormessagesforeacherror,wewillreturnthelastonebypoppingitfromtheerrorMessagesarray.
Theendresultshouldlookasfollows:
Fig.5
IfyouexperienceanyproblemsduringtheimplementationoftheControlErrorscomponent,youcantakealookatitsimplementationatch6/ts/multi-page-template-driven/add_developer.ts.
ThehasErrormethodofeverycontrolacceptsasanargumentanerrormessageidentifier,whichisdefinedbythevalidator.Forinstance,intheprecedingexamplewherewedefinedthecustome-mailvalidator,wewillreturnthefollowingobjectliteralwhentheinputcontrolhasaninvalidvalue:{'invalidEmail':true}.IfweapplytheControlErrorscomponenttothee-mailcontrol,itsdeclarationshouldlookasfollows:
<control-errorscontrol="email"
[errors]="{'invalidEmail':'Invalidemailaddress'}"/>
Two-waydata-bindingwithAngular2OneofthemostfamousrumorsaboutAngular2wasthatthetwo-waydata-bindingfunctionalitywasremovedbecauseoftheenforcedunidirectionaldataflow.Thisisnotexactlytrue;theAngular2’sformmoduleimplementsadirectivewiththeselector[(ngModel)],whichallowsustoeasilyachievedata-bindingintwodirections—fromtheviewtothemodelandfromthemodeltotheview.
Let’stakealookatthefollowingsimplecomponent:
//ch6/ts/simple-two-way-data-binding/app.ts
import{Component}from'angular2/core';
import{bootstrap}from'angular2/platform/browser';
import{NgModel}from'angular2/common';
@Component({
selector:'app',
directives:[NgModel],
template:`
<inputtype="text"[(ngModel)]="name"/>
<div>{{name}}</div>
`,
})
classApp{
name:string;
}
bootstrap(App,[]);
Intheprecedingexample,weimportedthedirectiveNgModelfromtheangular2/commonpackage.Later,inthetemplate,wesettheattribute[(ngModel)]tothevaluename.
Atfirst,thesyntax[(ngModel)]mightseemalittlebitunusual.FromChapter4,GettingStartedwithAngular2ComponentsandDirectives,weknowthatthesyntax(eventName)isusedforbindingtoevents(oroutputs)triggeredbyagivencomponent.Ontheotherhand,weusethesyntax[propertyName]="foobar"toachieveone-waydata-bindingbysettingthevalueoftheproperty(orintheterminologyoftheAngular2components,theinput)withthenamepropertyNametotheresultoftheevaluationoftheexpressionfoobar.ThesyntaxNgModeljustcombinesbothinordertoachievedata-bindingintwodirections.That’swhywecanthinkofitmorelikeasyntaxsugar,ratherthananewconcept.OneofthemainadvantagesofthissyntaxcomparedtoAngularJS1.xisthatwecantellwhichbindingsareone-wayandwhicharetwo-wayonlybylookingatthetemplate.
NoteJustlike(click)hasitscanonicalsyntaxon-clickand[propertyName]hasbind-propertyName,thealternativesyntaxof[(ngModel)]isbindon-ngModel.
Ifyouopenhttp://localhost:5555/dist/dev/ch6/ts/simple-two-way-data-
binding/,youwillseethefollowingresult:
Fig.6
Onceyouchangethevalueoftheinputbox,itwillautomaticallyupdatethefollowinglabel.
WealreadyusedtheNgModeldirectiveintheprecedingtemplates.Forexample,weboundtothedeveloper’se-mailusing:
<inputid="emailInput"
class="form-control"type="text"
ngControl="email"[(ngModel)]="developer.email"
email-input/>
Thisway,thevalueofthee-mailpropertyofthedeveloperobjectattachedtotheAddDevelopercomponent’sinstanceisgoingtobeupdatedoncewechangethevalueofthetextinput.
StoringtheformdataLet’speekattheinterfaceoftheAddDevelopercomponent’scontrolleragain:
exportclassAddDeveloper{
submitted:false;
successMessage:string;
developer=newDeveloper();
//…
constructor(privatedevelopers:DeveloperCollection){}
addDeveloper(form){…}
}
IthasafieldofthetypeDeveloper,andwebindtheformcontrolstoitspropertiesusingtheNgModeldirective.TheclassalsohasamethodcalledaddDeveloper,whichisbeinginvokedonthesubmissionoftheform.Wedeclarethisbybindingtothesubmiteventusing:
<!--ch6/ts/multi-page-template-driven/add_developer.html-->
<form#f="form"(ngSubmit)="addDeveloper()"
class="formcol-md-4"[hidden]="submitted">
…
<buttonclass="btnbtn-default"
type="submit"[disabled]="!f.form.valid">Add</button>
</form>
Intheprecedingsnippet,wecannoticetwomorethings.Wegotareferencetotheformusing#f="ngForm"andweboundthedisabledpropertyofthebuttontotheexpression:!f.form.valid.WealreadydescribedtheNgFormcontrolintheprevioussection;itsvalidpropertywillhaveavaluetrueonceallthecontrolswithintheformhavevalidvalues.
Now,let’ssupposewe’veenteredvalidvaluesforalltheinputcontrolsintheform.Thismeansthatitssubmitbuttonwillbeenabled.OncewepressEnterorweclickontheAddbutton,theaddDevelopermethodwillbeinvoked.Thefollowingisasampleimplementationofthismethod:
classAddDeveloper{
//…
addDeveloper(){
this.developer.id=this.developers.getAll().length+1;
this.developers.addDeveloper(this.developer);
this.successMessage=`Developer${this.developer.realName}was
successfullyadded`;
this.submitted=true;
}
Initially,wesettheidpropertyofthecurrentdevelopertoequalthetotalnumberofdevelopersinDeveloperCollection,plusone.Later,weaddedthedevelopertothecollectionandsetthevalueofthesuccessMessageproperty.Rightafterthis,wesetthepropertysubmittedtoequaltotrue,whichwillresultinhidingtheform.
ListingallthestoreddevelopersNowthatwecanaddanewentrytothedevelopers’collection,let’sshowalistofallthedevelopersonthefrontpageofthe“Codersrepository”.
Openthefilech6/ts/step-1/home.tsandenterthefollowingcontent:
import{Component}from'angular2/core';
import{DeveloperCollection}from'./developer_collection';
@Component({
selector:'home',
templateUrl:'./home.html'
})
exportclassHome{
constructor(privatedevelopers:DeveloperCollection){}
getDevelopers(){
returnthis.developers.getAll();
}
}
Thereisnothingnewtoushere.WeextendthefunctionalityoftheHomecomponentbyprovidinganexternaltemplateandimplementingthegetDevelopersmethod,whichdelegatesitscalltotheinstanceofDeveloperCollectionthatisinjectedintheconstructor.
Thetemplateitselfissomethingthatwe’realreadyfamiliarwithaswell:
<tableclass="table"*ngIf="getDevelopers().length>0">
<thead>
<th>Email</th>
<th>Realname</th>
<th>Technology</th>
<th>Popular</th>
</thead>
<tr*ngFor="#devofgetDevelopers()">
<td>{{dev.email}}</td>
<td>{{dev.realName}}</td>
<td>{{dev.technology}}</td>
<td[ngSwitch]="dev.popular">
<span*ngSwitchWhen="true">Yes</span>
<span*ngSwitchWhen="false">Notyet</span>
</td>
</tr>
</table>
<div*ngIf="getDevelopers().length==0">
Therearenoanydevelopersyet
</div>
WelistallthedevelopersasrowswithinanHTMLtable.Foreachdeveloper,wecheckthestatusofitspopularflag.Ifitsvalueistrue,thenforthePopularcolumn,weshowaspanwiththetextYes,otherwisewesetthetexttoNo.
WhenyouenterafewdevelopersintheAddDeveloperpageandyounavigatetothe
homepageafterthat,youshouldseearesultsimilartothefollowingscreenshot:
Fig.7
NoteYoucanfindthecompletefunctionalityoftheapplicationatch6/ts/multi-page-template-driven.
SummarySofar,wehaveexplainedthebasicsofroutinginAngular2.Wetookalookathowwecandefinedifferentroutesandimplementthecomponentsassociatedwiththemthataredisplayedonroutechange.Inordertolinktothedifferentroutes,weexplainedrouterLink,andwealsousedtherouter-outletdirectivesforpointingoutwherethecomponentsassociatedwiththeindividualroutesshouldberendered.
AnotherthingwetookalookatwastheAngular2formsfunctionalitywithbuilt-inandcustomvalidation.Afterthis,weexplainedtheNgModeldirective,whichprovidestoustwo-waydata-binding.
Inthenextchapter,wewillcoverhowwecandevelopmodel-drivenformsandchildandparametrizedroutes,usetheHttpmoduleformakingRESTfulcalls,andtransformdatawithcustompipes.
Chapter7.ExplainingPipesandCommunicatingwithRESTfulServicesInthelastchapter,wecoveredsomeverypowerfulfeaturesoftheframework.However,wecangoevendeeperintothefunctionalityofAngular’sformsmoduleandrouter.Inthenextsections,we’llexplainhowwecan:
Developmodel-drivenforms.Defineparameterizedroutes.Definechildroutes.UsetheHttpmoduleforcommunicationwithRESTfulAPIs.Transformdatawithcustompipes.
Wewillexplorealltheseconceptsintheprocessofextendingthefunctionalityofthe“Codersrepository”application.Atthebeginningofthepreviouschapter,wementionedthatwe’regoingtoallowimportofdevelopersfromGitHub.Butbeforeweimplementthisfeature,let’sextendthefunctionalityoftheform.
Developingmodel-drivenformsinAngular2Thesearegoingtobethelaststepsinfinishingthe“Codersrepository”.Youcanbuildontopofthecodeatch6/ts/step-1/(orch6/ts/step-2dependingonyourpreviouswork)inordertoextendtheapplication’sfunctionalitywiththenewconceptswe’regoingtocover.Thecompleteexampleislocatedatch7/ts/multi-page-model-driven.
Thisistheresultthatwearegoingtoachievebytheendofthissection:
Intheprecedingscreenshot,therearethefollowingtwoforms:
AformforimportingexistingusersfromGitHubthatcontains:
TheinputfortheGitHubhandle.AcheckboxthatpointsoutwhetherwewanttoimportthedeveloperfromGitHuborenteritmanually.
Aformforenteringnewusersmanually.
Thesecondformlooksexactlythewaywefinisheditinthelastsection.However,this
time,itsdefinitionlooksalittlebitdifferent:
<formclass="formcol-md-4"
[ngFormModel]="addDevForm"[hidden]="submitted">
<!--TODO-->
</form>
Noticethatthistime,wedon’thavethesubmithandlerorthe#f="ngForm"attribute.Instead,weusethe[ngFormModel]attributeinordertobindtoapropertydefinedinsidethecomponent’scontroller.Byusingthisattribute,wecanbindtosomethingcalledControlGroup.Asitsnamestates,theControlGroupclassconsistsofalistofcontrolsgroupedtogetherwiththesetsofvalidationrulesassociatedwiththem.
Weneedtouseasimilardeclarationtoimportadeveloperform.However,thistime,wewillprovideadifferentvalueofthe[ngFormModel]attribute,sincewearegoingtodefineadifferentcontrolgroupinthecomponent’scontroller.Placethefollowingsnippetabovetheformweintroducedearlier:
<formclass="formcol-md-4"
[ngFormModel]="importDevForm"[hidden]="submitted">
<!--TODO-->
</form>
Now,let’sdeclaretheimportDevFormandaddDevFormpropertiesinthecomponent’scontroller:
import{ControlGroup}from'angular2/common';
@Component(…)
exportclassAddDeveloper{
importDevForm:ControlGroup;
addDevForm:ControlGroup;
…
constructor(privatedevelopers:DeveloperCollection,
fb:FormBuilder){…}
addDeveloper(){…}
}
Initially,weimportedtheControlGroupclassfromtheangular2moduleand,later,declaredtherequiredpropertiesinthecontroller.Let’salsonoticethatwehaveoneadditionalparameteroftheconstructorofAddDevelopercalledfbofthetypeFormBuilder.
FormBuilderprovidesaprogrammableAPIforthedefinitionofControlGroupswherewecanattachvalidationbehaviortoeachcontrolinthegroup.Let’susetheFormBulderinstancefortheinitializationoftheimportDevFormandaddDevFormproperties:
…
constructor(privatedevelopers:DeveloperCollection,
fb:FormBuilder){
this.importDevForm=fb.group({
githubHandle:['',Validators.required],
fetchFromGitHub:[false]
});
this.addDevForm=fb.group({
realName:['',Validators.required],
email:['',validateEmail],
technology:['',Validators.required],
popular:[false]
});
}
…
TheFormBuilderinstancehasamethodcalledgroupthatallowsustodefineproperties,suchasthedefaultvaluesandthevalidatorsfortheindividualcontrolsinagivenform.
Accordingtotheprecedingsnippet,importDevFormhastwofieldsthatweintroducedearlier:githubHandleandfetchFromGitHub.WedeclaredthatthevalueofthegithubHandlecontrolisrequiredandsetthedefaultvalueofthecontrolfetchFromGitHubtofalse.
Inthesecondform,addDevForm,wedeclarefourcontrols.FortherealNamecontrolasthedefaultvalue,wesettheemptystringanduseValidators.requredinordertointroducevalidationbehavior(whichisexactlywhatwedidforthegithubHandlecontrol).Asavalidatorforthee-mailinput,wewillusethevalidateEmailfunctionandsetitsinitialvaluetoanemptystring.ThevalidateEmailfunctionusedforvalidationistheonewedefinedinthepreviouschapter:
functionvalidateEmail(emailControl){
if(!emailControl.value||
/^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-
9-.]+$/.test(emailControl.value)){
returnnull;
}else{
return{'invalidEmail':true};
}
}
Thelasttwocontrolswedefineherearethetechnologycontrol,whichvalueisrequiredandhasanemptystringasitsinitialvalue,andthepopularcontrolwithitsinitialvaluesettofalse.
UsingcompositionofcontrolvalidatorsWetookalookathowwecanapplyasinglevalidatortoformcontrols.However,insomeapplications,thedomainmayrequiremorecomplexvalidationlogic.Forexample,ifwewanttoapplyboththerequiredandthevalidateEmailvalidatorstothee-mailcontrol,weshoulddothefollowing:
this.addDevForm=fb.group({
…
email:['',Validators.compose([
Validators.required,
validateEmail]
)],
…
});
ThecomposemethodoftheValidatorsobjectacceptsasanargumentanarrayofvalidatorsandreturnsanewvalidator.Thenewvalidator’sbehaviorisgoingtobeacompositionofthelogicdefinedintheindividualvalidatorspassedasanargument,andtheyaregoingtobeappliedinthesameorderastheywereintroducedinthearray.
ThepropertynamesintheobjectliteralpassedtothegroupmethodshouldmatchwiththevaluesthatwesettothengControlattributesoftheinputsinthetemplate.
ThisisthecompletetemplateofimportDevForm:
<formclass="formcol-md-4"
[ngFormModel]="importDevForm"[hidden]="submitted">
<divclass="form-group">
<labelclass="control-label"
for="githubHandleInput">GitHubhandle</label>
<div>
<inputid="githubHandleInput"
class="form-control"type="text"
[disabled]="!fetchFromGitHub"
ngControl="githubHandle">
<control-errorscontrol="githubHandle"
[errors]="{
'required':'TheGitHubhandleisrequired'
}"></control-errors>
</div>
</div>
<divclass="form-group">
<labelclass="control-label"
for="fetchFromGitHubCheckbox">
FetchfromGitHub
</label>
<inputclass="checkbox-inline"id="fetchFromGitHubCheckbox"
type="checkbox"ngControl="fetchFromGitHub"
[(ngModel)]="fetchFromGitHub">
</div>
</form>
Intheprecedingtemplate,youcannoticethatoncetheflagsubmittedhasthevaluetrue,
sotheformwillbehiddenfromtheuser.Nexttothefirstinputelement,wesetthevalueofthengControlattributetogithubHandle.
NoteNotethatthevalueofthengControlattributeofthegiveninputelementmustmatchthenameweusedforitscorrespondingcontroldeclarationinthedefinitionofControlGroupwithinthecomponent’scontroller.
WithregardtothegithubHandlecontrol,wealsosetthedisabledattributetoequaltheresultoftheevaluationoftheexpression:!fetchFromGitHub.Thisway,whenthefetchFromGitHubcheckboxisunchecked,thegithubHandlecontrolwillbedisabled.Similarly,incaseoftheexampleintheprevioussections,weusedtheControlErrorscomponentwedefinedpreviously.Thistime,wesetasingleerrorwiththemessageTheGitHubhandleisrequired.
ThemarkupfortheformaddDevFormlooksquitesimilar,sowewon’tdescribeitindetailhere.Ifyou’renotcompletelysureofhowtoapproachdevelopingit,youcantakealookatthecompleteimplementationatch7/ts/multi-page-model-driven/add_developer.html.
Thelastpartofthetemplatewe’regoingtotakealookatistheSubmitbutton:
<buttonclass="btnbtn-default"
(click)="addDeveloper()"
[disabled]="(fetchFromGitHub&&!importDevForm.valid)||
(!fetchFromGitHub&&!addDevForm.valid)">
Add
</button>
ClickingonthebuttonwillinvoketheaddDevelopermethoddefinedinthecomponent’scontroller.Intheexpressionsetasvalueofthe[disabled]attribute,weinitiallycheckwhichformisselectedbyusingthevalueofthepropertyboundtothecheckbox,thatis,weverifywhethertheuserwantstoaddanewdeveloperorimportanexistingonefromGitHub.Ifthefirstoptionisselected(thatis,ifthecheckboxisnotchecked),weverifywhethertheControlGroupforaddinganewdeveloperisvalid.Ifitisvalid,thenthebuttonwillbeenabled,otherwiseitwillbedisabled.WewilldothesameincaseswhentheuserhascheckedthecheckboxforimportingadeveloperfromGitHub.
ExploringtheHTTPmoduleofAngularNow,afterwedeveloptheformsforbothimportingexistingandaddingnewdevelopers,itisthetimetoimplementthelogicbehinditinthecontrollerofthecomponent.
Forthispurpose,weneedtocommunicatewiththeGitHubAPI.Althoughwecandothisdirectlyfromthecomponent’scontroller,bydoingitthisway,wecancoupleitwiththeRESTfulAPIofGitHub.Inordertoenforcefurtherseparationofconcerns,wecanextractthelogicforcommunicationwithGitHubintoaseparateservicecalledGitHubGateway.Openafilecalledgithub_gateway.tsandenterthefollowingcontent:
import{Injectable}from'angular2/core';
import{Http}from'angular2/http';
@Injectable()
exportclassGitHubGateway{
constructor(privatehttp:Http){}
getUser(username:string){
returnthis.http
.get(`https://api.github.com/users/${username}`);
}
}
Initially,weimportedtheHttpclassfromtheangular2/httpmodule.AlltheHTTP-relatedfunctionalityisexternalizedandisoutsidetheAngular’score.SinceGitHubGatewayacceptsadependency,whichneedstobeinjectedthroughtheDImechanismoftheframework,wewilldecorateitwiththe@Injectabledecorator.
TheonlyfunctionalityfromtheGitHub’sAPIwe’regoingtouseistheoneforfetchingusers,sowewilldefineasinglemethodcalledgetUser.Asanargument,itacceptstheGitHubhandleofthedeveloper.
NoteNotethatifyoumakemorethan60requestsperdaytotheGitHub’sAPI,youmightgettheerrorGitHubAPIRatelimitexceeded.ThisisduetotheratelimitsforrequestswithoutaGitHubAPItoken.Forfurtherinformation,visithttps://github.com/blog/1509-personal-api-tokens.
InsidethegetUsermethod,weusetheinstanceoftheHttpservicethatwe’vereceivedintheconstructorfunction.TheHttpservice’sAPIstaysasclosetotheHTML5fetchAPIaspossible.However,thereareacoupleofdifferences.Themostsignificantoneofthemisthatatthemomentofwritingthiscontent,allthemethodsoftheHttpinstancesreturnObservablesinsteadofPromises.
TheHttpserviceinstanceshavethefollowingAPI:
request(url:string|Request,options:RequestOptionsArgs):MakesarequesttothespecifiedURL.TherequestcanbeconfiguredusingRequestOptionsArgs:
http.request('http://example.com/',{
method:'get',
search:'foo=bar',
headers:newHeaders({
'X-Custom-Header':'Hello'
})
});
get(url:string,options?:RequestOptionsArgs):MakesagetrequesttothespecifiedURL.Therequestheadersandotheroptionscanbeconfiguredusingthesecondargument.post(url:string,options?:RequestOptionsArgs):MakesapostrequesttothespecifiedURL.Therequestbody,headers,andotheroptionscanbeconfiguredusingthesecondargument.put(url:string,options?:RequestOptionsArgs):MakesaputrequesttothespecifiedURL.Therequestheadersandotheroptionscanbeconfiguredusingthesecondargument.patch(url:string,options?:RequestOptionsArgs):MakesapatchrequesttothespecifiedURL.Therequestheadersandotheroptionscanbeconfiguredusingthesecondargument.delete(url:string,options?:RequestOptionsArgs):MakesadeleterequesttothespecifiedURL.Therequestheadersandotheroptionscanbeconfiguredusingthesecondargument.head(url:string,options?:RequestOptionsArgs):MakesaheadrequesttothespecifiedURL.Therequestheadersandotheroptionscanbeconfiguredusingthesecondargument.
UsingAngular’sHTTPmoduleNow,let’simplementthelogicforimportingexistingusersfromGitHub!Openthefilech6/ts/step-2/add_developer.tsandenterthefollowingimports:
import{Response,HTTP_PROVIDERS}from'angular2/http';
import{GitHubGateway}from'./github_gateway';
AddHTTP_PROVIDERSandGitHubGatewaytothelistofprovidersoftheAddDevelopercomponent:
@Component({
…
providers:[GitHubGateway,FORM_PROVIDERS,HTTP_PROVIDERS]
})
classAddDeveloper{…}
Asthenextstep,wehavetoincludethefollowingparametersintheconstructoroftheclass:
constructor(privategithubAPI:GitHubGateway,
privatedevelopers:DeveloperCollection,
fb:FormBuilder){
//…
}
Thisway,theAddDeveloperclass’instanceswillhaveaprivatepropertycalledgithubAPI.
TheonlythingleftistoimplementtheaddDevelopermethodandallowtheusertoimportexistingdevelopersbyusingtheGitHubGatewayinstance.
OncetheuserpressestheAddbutton,weneedtocheckwhetherweneedtoimportanexistingGitHubuseroraddanewdeveloper.Forthispurpose,wecanusethevalueofthefetchFromGitHubcontrol:
if(this.importDevForm.controls['fetchFromGitHub'].value){
//Importdeveloper
}else{
//Addnewdeveloper
}
Ifithasatruthyvalue,thenwecaninvokethegetUsermethodofthegithubAPIpropertyandpassthevalueofthegithubHandlecontrolasanargument:
this.githubAPI.getUser(model.githubHandle)
InthegetUsermethod,wedelegatethecalltotheHttpservice’sgetmethod,whichreturnsanobservable.Inordertogettheresultthattheobservableisgoingtopush,weneedtopassacallbacktoitssubscribemethod:
this.githubAPI.getUser(model.githubHandle)
.map((r:Response)=>r.json())
.subscribe((res:any)=>{
//"res"containstheresponseoftheGitHub'sAPI
});
Intheprecedingsnippet,wefirstestablishtheHTTPgetrequest.Afterthis,we’llgettheobservablethat,ingeneralcases,willemitaseriesofvalues(inthiscase,onlyasingleone—theresponseoftherequest)andmapthemtotheJSONrepresentationoftheirbodies.IftheresponsefailsoritsbodyisnotavalidJSONstring,thenwewillgetanerror.
NoteNotethatinordertoreducethesizeofRxJS,Angular’scoreteamhasincludedonlyitscore.Inordertousethemethodsmapandcatch,youneedtoaddthefollowingimportsatadd_developer.ts:
import'rxjs/add/operator/map';
import'rxjs/add/operator/catch';
Nowlet’simplementthebodyofthesubscribecallback:
letdev=newDeveloper();
dev.githubHandle=res.login;
dev.email=res.email;
dev.popular=res.followers>=1000;
dev.realName=res.name;
dev.id=res.id;
dev.avatarUrl=res.avatar_url;
this.developers.addDeveloper(dev);
this.successMessage=`Developer${dev.githubHandle}successfullyimported
fromGitHub`;
Intheprecedingexample,wesetthepropertiesofanewDeveloperinstance.Here,weestablishedthemappingbetweentheobjectreturnedfromGitHub’sAPIandthedeveloper’srepresentationinourapplication.Wealsoconsideredadeveloperaspopularifsheorhehasabove1,000followers.
TheentireimplementationoftheaddDevelopermethodcanbefoundatch7/ts/multi-page-model-driven/add_developer.ts.
NoteInordertohandlefailedrequests,wecanusethecatchmethodoftheobservableinstances:
this.githubAPI.getUser(model.githubHandle)
.catch((error,source,caught)=>{
console.log(error)
returnerror;
})
DefiningparameterizedviewsAsthenextstep,let’sdedicateaspecialpageforeachdeveloper.Insideofit,we’llbeabletotakeadetailedlookathisorherprofile.Oncetheuserclicksonthenameofanyofthedevelopersonthehomepageoftheapplication,heorsheshouldberedirectedtoapagewithadetailedprofileoftheselecteddeveloper.Theendresultwilllookasfollows:
Inordertodothis,weneedtopassanidentifierofthedevelopertothecomponentthatshowsdeveloper’sdetailedprofile.Openapp.tsandaddthefollowingimport:
import{DeveloperDetails}from'./developer_details';
Wehaven’tdevelopedtheDeveloperDetailscomponentyet,soifyouruntheapplication,youwillgetanerror.Wewilldefinethecomponentinthenextparagraph,butbeforethis,let’salterthe@RouteConfigdefinitionoftheAppcomponent:
@RouteConfig([
//…
newRoute({
component:DeveloperDetails,
name:'DeveloperDetails',
path:'/dev-details/:id/...'
}),
//…
])
classApp{}
WeaddedasingleroutewiththeDeveloperDetailscomponentassociatedwithit,andasanalias,weusedthestring"DeveloperDetails".
Thevalueofthecomponentpropertyisareferencetotheconstructorofthecomponent,whichshouldhandlethegivenroute.Oncethesourcecodeoftheapplicationgetsminifiedforproduction,thecomponentnamemaydifferfromtheonewe’veentered.ThiswillcreateproblemswhenreferencingtheroutewithinthetemplatesusingtherouterLinkdirective.Inordertopreventthisfromhappening,thecoreteamintroducedthenamepropertythat,inthiscase,equalstothenameofthecontroller.
NoteAlthoughinalltheexamplessofar,wesetthealiasoftheroutetobethesameasthenameofthecomponent’scontroller,thisisnotrequired.Thisconventionisusedforsimplicity,sinceitcouldbeconfusingtointroducetwonames:oneforpointingtotherouteandanotheroneforthecontrollerassociatedwiththegivenroute.
Inthepathproperty,wedeclarethattheroutehasasingleparametercalledid,andwith"...",wehinttheframeworkthatthisroutewillhavenestedroutesinsideofit.
Now,let’spasstheidofthecurrentdeveloperasaparametertotherouterLinkdirective.Openhome.htmlinyourworkingdirectoryandreplacethetablecellwherewedisplaythedeveloper’srealNamepropertywiththefollowingcontent:
<td>
<a[routerLink]="['/DeveloperDetails',
{'id':dev.id},'DeveloperBasicInfo']">
{{dev.realName}}
</a>
</td>
ThevalueoftherouterLinkdirectiveisanarraywiththefollowingthreeelements:
'/DeveloperDetails':Astringthatshowstherootroute{'id':dev.id}:Anobjectliteralthatdeclarestherouteparameters'DeveloperBasicInfo':ThenameofaroutethatshowswhichcomponentwithinthenestedrouteinthecomponentwiththealiasDeveloperDetailsshouldberendered
DefiningnestedroutesNowlet’sjumptotheDeveloperDetailsdefinition.Inyourworkingdirectory,createafilecalleddeveloper_details.tsandenterthefollowingcontent:
import{Component}from'angular2/core';
import{
ROUTER_DIRECTIVES,
RouteConfig,
RouteParams
}from'angular2/router';
import{Developer}from'./developer';
import{DeveloperCollection}from'./developer_collection';
@Component({
selector:'dev-details',
template:`…`,
})
@RouteConfig(…)
exportclassDeveloperDetails{
publicdev:Developer;
constructor(routeParams:RouteParams,
developers:DeveloperCollection){
this.dev=developers.getUserById(
parseInt(routeParams.params['id'])
);
}
}
Intheprecedingsnippet,wedefinedacomponentwithcontrollercalledDeveloperDetails.Youcannoticethatwithinthecontroller’sconstructor,throughtheDImechanismofAngular2,weinjectedaparameterassociatedwiththeRouteParamstoken.Theinjectedparameterprovidesusaccesstotheparametersvisiblebythecurrentroute.Wecanaccessthemusingtheparamspropertyoftheinjectedobjectandaccessthetargetparameterusingitsnameasakey.
SincetheparameterwegotfromrouteParams.params['id']isastring,weneedtoparseittoanumberinordertogetthedeveloperassociatedwiththegivenroute.Nowlet’sdefinetheroutesassociatedwithDeveloperDetails:
@Component(…)
@RouteConfig([{
component:DeveloperBasicInfo,
name:'DeveloperBasicInfo',
path:'/'
},
{
component:DeveloperAdvancedInfo,
name:'DeveloperAdvancedInfo',
path:'/dev-details-advanced'
}])
exportclassDeveloperDetails{…}
Intheprecedingsnippet,thereisnothingnewforus.Theroutedefinitionfollowstheexactsameruleswe’realreadyfamiliarwith.
Now,tothetemplateofthecomponent,let’saddlinksassociatedwiththeindividualnestedroutes:
@Component({
selector:'dev-details',
directives:[ROUTER_DIRECTIVES],
template:`
<sectionclass="col-md-4">
<ulclass="navnav-tabs">
<li>
<a[routerLink]="['./DeveloperBasicInfo']">
Basicprofile
</a>
</li>
<li>
<a[routerLink]="['./DeveloperAdvancedInfo']">
Advanceddetails
</a>
</li>
</ul>
<router-outlet/>
</section>
`,
})
@RouteConfig(…)
exportclassDeveloperDetails{…}
Withinthetemplate,wedeclaretworelativetothecurrentpathlinks.ThefirstonepointstoDeveloperBaiscInfo,whichisthenameofthefirstroutedefinedwithin@RouteConfigoftheDeveloperDetailscomponent,andrespectively,thesecondonepointstoDeveloperAdvancedInfo.
Sincetheimplementationsofboththecomponentsarequitesimilar,let’stakealookonlyatDeveloperBasicInfo.Asanexercise,youcandevelopthesecondoneortakealookatitsimplementationatch7/ts/multi-page-model-driven/developer_advanced_info.ts:
import{
Component,
Inject,
forwardRef,
Host
}from'angular2/core';
import{DeveloperDetails}from'./developer_details';
import{Developer}from'./developer';
@Component({
selector:'dev-details-basic',
styles:[…],
template:`
<h2>{{dev.realName}}</h2>
<img*ngIf="dev.avatarUrl==null"
class="avatar"src="./gravatar-60-grey.jpg"width="150">
<img*ngIf="dev.avatarUrl!=null"
class="avatar"[src]="dev.avatarUrl"width="150">
`
})
exportclassDeveloperBasicInfo{
dev:Developer;
constructor(@Inject(forwardRef(()=>DeveloperDetails))
@Host()parent:DeveloperDetails){
this.dev=parent.dev;
}
}
Intheprecedingsnippet,weinjectedtheparentcomponentcombiningthe@[email protected]@Inject,weuseforwardRef,sincewehaveacirculardependencybetweenthepackagesdeveloper_basic_infoanddeveloper_details(insidedeveloper_basic_info,weimportdeveloper_details,andwithindeveloper_details,weimportdeveloper_basic_info).
Weneedareferencetotheinstanceoftheparentcomponentinordertogettheinstanceofthecurrentdevelopercorrespondingtotheselectedroute.
TransformingdatawithpipesItistimeforthelastbuildingblockthatAngular2providesforthedevelopmentofapplicationsthatwehaven’tcoveredindetailyet—thepipes.
JustlikethefiltersinAngularJS1.x,pipesareintendedtoencapsulateallthedata-transformationlogic.Let’stakealookatthetemplateofthehomepageoftheapplicationwejustdeveloped:
…
<td[ngSwitch]="dev.popular">
<span*ngSwitch-when="true">Yes</span>
<span*ngSwitch-when="false">Notyet</span>
</td>
…
Intheprecedingsnippet,dependingonthevalueofthepopularproperty,weshoweddifferentdatausingtheNgSwitchandNgSwitchThendirectives.Althoughthisworks,itisredundant.
DevelopingstatelesspipesLet’sdevelopapipethattransformsthevalueofthepopularpropertyandusesitintheplaceofNgSwitchandNgSwitchThen.Thepipewillacceptthreearguments:avaluethatshouldbetransformed,astringthatshouldbedisplayedwhenthevalueistruthy,andanotherstringthatshouldbedisplayedincaseofafalsyvalue.
WiththeuseofanAngular2custompipe,wewillbeabletosimplifythetemplateto:
<td>{{dev.popular|boolean:'Yes':'No'}}</td>
Wecouldevenuseemojis:
<td>{{dev.popular|boolean:'':''}}</td>
WeapplythepipetothevaluethesamewaywedoinAngularJS1.x.Theargumentswepasstothepipeshouldbeseparatedbythecolon(:)symbol.
InordertodevelopanAngular2pipe,weneedthefollowingimports:
import{Pipe,PipeTransform}from'angular2/core';
ThePipedecoratorcanbeusedforaddingmetadatatotheclassthatimplementsthedatatransformationlogic.ThePipeTransformisaninterfacewithasinglemethodcalledtransform:
import{Pipe,PipeTransform}from'angular2/core';
@Pipe({
name:'boolean'
})
exportclassBooleanPipeimplementsPipeTransform{
constructor(){}
transform(flag:boolean,args:string[]):string{
returnflag?args[0]:args[1];
}
}
TheprecedingsnippetistheentireimplementationofBooleanPipe.Thenameofthepipedeterminesthewayitshouldbeusedintemplates.
ThelastthingweneedtodobeforebeingabletousethepipeistoaddtheBooleanPipeclasstothelistofpipesusedbytheHomecomponent(BooleanPipealreadyholdsthemetadataattachedtoitbythe@Pipedecorator,soitsnameisattachedtoit):
@Component({
…
pipes:[BooleanPipe],
})
exportclassHome{
constructor(privatedevelopers:DeveloperCollection){}
getDevelopers(){…}
}
UsingAngular’sbuilt-inpipesAngular2providesthefollowingsetofbuilt-inpipes:
CurrencyPipe:Thispipeisusedforformattingcurrencydata.Asanargument,itacceptstheabbreviationofthecurrencytype(thatis,"EUR","USD",andsoon).Itcanbeusedinthefollowingway:
{{currencyValue|currency:'USD'}}<!--USD42-->
DatePipe:Thispipeisusedforthetransformationofdates.Itcanbeusedinthefollowingway:
{{dateValue|date:'shortTime'}}<!--12:00AM-->
DecimalPipe:Thispipeisusedfortransformationofdecimalnumbers.Theargumentitacceptsisofthefollowingform:"{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}".Itcanbeusedinthefollowingway:
{{42.1618|number:'3.1-2'}}<!--042.16-->
JsonPipe:ThistransformsaJavaScriptobjectintoaJSONstring.Itcanbeusedinthefollowingway:
{{{foo:42}|json}}<!--{"foo":42}-->
LowerCasePipe:Thistransformsastringtolowercase.Itcanbeusedinthefollowingway:
{{FOO|lowercase}}<!--foo-->
UpperCasePipe:Thistransformsastringtouppercase.Itcanbeusedinthefollowingway:
{{'foo'|uppercase}}<!--FOO-->
PercentPipe:Thistransformsanumberintoapercentage.Itcanbeusedinthefollowingway:
{{42|percent:'2.1-2'}}<!--4,200.0%-->
SlicePipe:Thisreturnsasliceofanarray.Thepipeacceptsthestartandtheendindexesoftheslice.Itcanbeusedinthefollowingway:
{{[1,2,3]|slice:1:2}}<!--2-->
AsyncPipe:Thisisastatefulpipethatacceptsanobservableorapromise.We’regoingtotakealookatitattheendofthechapter.
DevelopingstatefulpipesTherewasonecommonthingbetweenallthepipesmentionedearlier—allofthemreturnexactlythesameresulteachtimeweapplythemtothesamevalueandpassthemthesamesetofarguments.Suchpipesthatholdthereferentiallytransparencypropertyarecalledpurepipes.
The@Pipedecoratoracceptsanobjectliteralofthefollowingtype:{name:string,pure?:boolean},wherethedefaultvalueforthepurepropertyistrue.Thismeansthatwhenwedecorateagivenclassusingthe@Pipedecorator,wecandeclarewhetherwewantthepipeitimplementsthelogicfortobeeitherstatefulorstateless.Thepurepropertyisimportant,becauseincasethepipeisstateless(thatis,itreturnsthesameresultincaseitisappliedoverthesamevaluewiththesamesetofarguments),thechangedetectioncanbeoptimized.
Nowlet’sbuildastatefulpipe!OurpipewillmakeanHTTPgetrequesttoaJSONAPI.Forthispurpose,wewillusetheangular2/httpmodule.
NoteNotethathavingbusinesslogicinapipeisnotconsideredasabestpractice.Thistypeoflogicshouldbeextractedintoaservice.Theexamplehereisforlearningpurposesonly.
Inthiscase,thepipeneedstoholdadifferentstatedependingonthestatusoftherequest(thatis,whetheritispendingorcompleted).Wewillusethepipeinthefollowingway:
{{"http://example.com/user.json"|fetchJson|json}}
Thisway,weapplythefetchJsonpipeovertheURL,andoncewehaveresponsefromtheremoteserviceandthepromisefortherequesthasbeenresolved,wecanapplythejsonpipeovertheobjectwegotfromtheresponse.TheexamplealsoshowshowwecanchainpipeswithAngular2.
Similarly,incaseofthepreviousexample,forthedevelopmentofastatelesspipe,wehavetoimportPipeandPipeTransform.However,thistime,becauseoftheHTTPrequestfunctionality,wealsoneedtoimporttheHttpandResponseclassesfromthe'angular2/http'module:
import{Pipe,PipeTransform}from'angular2/core';
import{Http,Response}from'angular2/http';
import'rxjs/add/operator/toPromise';
EachtimeithappenstoapplythefetchJsonpipetoadifferentargumentcomparedtotheonewegotinthepreviousinvocation,weneedtomakeanewHTTPgetrequest.Thismeansthatasthestateofthepipe,weneedtokeepatleastthevaluesoftheresponseoftheremoteserviceandthelastURL:
@Pipe({
name:'fetchJson',
pure:false
})
exportclassFetchJsonPipeimplementsPipeTransform{
privatedata:any;
privateprevUrl:string;
constructor(privatehttp:Http){}
transform(url:string):any{…}
}
Theonlypieceoflogicleftisthetransformmethod:
…
transform(url:string):any{
if(this.prevUrl!==url){
this.http.get(url).toPromise(Promise)
.then((data:Response)=>data.json())
.then(result=>this.data=result);
this.prevUrl=url;
}
returnthis.data||{};
}
…
Insideofit,weinitiallycomparedtheURLpassedasanargumentwiththeonewecurrentlykeepareferenceto.Iftheyaredifferent,weinitiateanewHTTPgetrequestusingthelocalinstanceoftheHttpclass,whichwaspassedtotheconstructorfunction.Oncetherequestiscompleted,weparsetheresponsetoJSONandsetthedatapropertytotheresult.
Now,let’ssupposethepipehasstartedanHttpgetrequest,andbeforeitiscompleted,thechangedetectionmechanisminvokesthepipeagain.Inthiscase,wewillcomparetheprevUrlpropertywiththeurlparameter.Incasetheyarethesame,wewon’tperformanewhttprequest,andwewillimmediatelyreturnthevalueofthedataproperty.IncaseprevUrlhasadifferentvaluefromurl,wewillstartanewrequest.
UsingstatefulpipesNowlet’susethepipethatwedeveloped!Theapplicationthatwearegoingtoimplementprovidestotheuseratextinputandabutton.Oncetheuserentersavalueinthetextinputandpressesthebutton,belowthetextinputwillappeartheavatarcorrespondingtotheGitHubuser,asshowninthefollowingscreenshot:
Now,let’sdevelopasamplecomponent,whichwillallowustoentertheGitHubuser’shandle:
//ch7/ts/statful_pipe/app.ts
@Component({
selector:'app',
providers:[HTTP_PROVIDERS],
pipes:[FetchJsonPipe,ObjectGetPipe],
template:`
<inputtype="text"#input>
<button(click)="setUsername(input.value)">GetAvatar</button>
`
})
classApp{
username:string;
setUsername(user:string){
this.username=user;
}
}
Intheprecedingexample,weaddedFetchJsonPipeusedbytheAppcomponent.TheonlythingleftistoshowtheGitHubavataroftheuser.Wecaneasilyachievethisbyalteringthetemplateoftheprecedingcomponentwiththefollowingimgdeclaration:
<imgwidth="160"[src]="(('https://api.github.com/users/'+username)|
fetchJson).avatar_url">
Initially,weappendedtheGitHubhandletothebaseURLusedforfetchingusersfromtheAPI.Later,weappliedthefetchJsonfilteroverit,andfromthereturnedresult,wegottheavatar_urlproperty.
NoteAlthoughthepreviousexampleworks,itisunnaturaltohavebusinesslogicinyourpipes.ItwillbefarbettertoimplementthelogicforcommunicationwiththeGitHub’sAPIintoaserviceor,atleast,invokethegetmethodoftheinstanceoftheHttpclassinacomponent.
UsingAngular’sAsyncPipeAngular’sAsyncPipetransformmethodacceptsasanargumentanobservableorapromise.Oncetheargumentpushesavalue(thatis,thepromisehasbeenresolvedorthesubscribecallbackoftheobservableisinvokedwithavalue),AsyncPipewillreturnitasaresult.Let’stakealookatthefollowingexample:
//ch7/ts/async-pipe/app.ts
@Component({
selector:'greeting',
template:'Hello{{greetingPromise|async}}'
})
classGreeting{
greetingPromise=newPromise<string>(resolve=>this.resolve=resolve);
resolve:Function;
constructor(){
setTimeout(_=>{
this.resolve('Foobar!');
},3000);
}
}
Here,wedefinedanAngular2component,whichhastwoproperties:greetingPromiseofthetypePromise<string>andresolveofthetypeFunction.WeinitializedthegreetingPromisepropertywithanewPromise<string>instance,andasvalueoftheresolveproperty,wesettheresolvecallbackofthepromise.
Intheconstructoroftheclass,westartatimeoutwiththedurationof3,000ms,andinsideofitscallback,weresolvethepromise.Oncethepromiseisresolved,thevalueoftheexpression{{greetingPromise|async}}willbeevaluatedtothestringFoobar!.TheendresultthattheuserwillseeonthescreenisthetextHelloFoobar!.
TheasyncpipeisextremelypowerfulwhenwecombineitwithanHttprequestortogetherwithanobservable,whichpushesasequenceofvalues.
UsingAsyncPipewithobservablesWe’realreadyfamiliarwiththeconceptofobservablesfromthepreviouschapters.Wecansaythatanobservableobjectallowsustosubscribetotheemissionofasequenceofvalues,forinstance:
letobserver=newObservable<number>(observer=>{
setInterval(()=>{
observer.next(newDate().getTime());
},1000);
});
observer.subscribe(date=>console.log(date));
Oncewesubscribetotheobservable,itwillstartemittingvalueseachsecond,whicharegoingtobeprintedintheconsole.Let’scombinethissnippetwiththecomponent’sdefinitionandimplementasimpletimer:
//ch7/ts/async-pipe/app.ts
@Component({
selector:'timer'
})
classTimer{
username:string;
timer:Observable<number>;
constructor(){
letcounter=0;
this.timer=newObservable<number>(observer=>{
setInterval(()=>{
observer.next(newDate().getTime());
},1000);
});
}
}
Theonlythingleftinordertobeabletousethetimercomponentistoadditstemplate.Wecansubscribetotheobservabledirectlyinourtemplatebyusingtheasyncpipe:
{{timer|async|date:"medium"}}
Thisway,eachsecondwewillgetthenewvalueemittedbytheobservable,andthedatepipewilltransformitintoareadableform.
SummaryInthischapter,wetookadeepdiveintotheAngular2formsbydevelopingamodel-drivenoneandcombiningitwiththehttpmoduleinordertobeabletoadddeveloperstoourrepository.Wetookalookatsomeadvancedfeaturesofthenewcomponent-basedrouterandsawhowwecanuseanddevelopourcustomizedstatefulandstatelesspipes.
ThenextchapterwillbededicatedtohowwecanmakeourAngular2applicationsSEO-friendlybytakingadvantageoftheserver-siderenderingthatthemoduleuniversalprovides.Wewillalsotakealookatangular-cliandtheothertoolsthatmakeourexperienceasdevelopersbetter.
Chapter8.DevelopmentExperienceandServer-SideRenderingWearealreadyfamiliarwithallthecoreconceptsofAngular2.Weknowhowtodevelopacomponent-baseduserinterface,takingadvantageofallthebuildingblocksthattheframeworkprovides—directives,components,dependencyinjections,pipes,forms,andthebrandnewcomponent-basedrouter.
Forthenextstep,we’lllookatwheretobeginwhenwewanttobuildasingle-pageapplication(SPA)fromscratch.Thischapterdescribeshowtodothefollowing:
UseWebWorkersforperformance-sensitiveapplications.BuildSEO-friendlyapplicationswithserver-siderendering.Bootstrapaprojectasquicklyaspossible.Enhanceourexperienceasdevelopers.
So,let’sbegin!
RunningapplicationsinWebWorkersWhentalkingaboutperformanceinthecontextoffrontendwebdevelopment,wecaneithermeannetwork,computational,orrenderingperformance.Inthissection,we’llconcentrateonrenderingandcomputationalperformance.
First,let’smakeaparallelbetweenawebapplicationandavideo,andbetweenabrowserandavideoplayer.Thebiggestdifferencebetweenthewebapplicationrunninginthebrowserandthevideofileplayinginthevideoplayeristhatthewebpageneedstobegenerateddynamically,incontrasttothevideowhichhasbeenrecorded,encoded,anddistributed.However,inboththecases,theuseroftheapplicationseesasequenceofframes;thecoredifferenceishowtheseframesarebeinggenerated.Intheworldofvideoprocessing,whenweplayavideo,wehaveitalreadyrecorded;itistheresponsibilityofthevideodecodertoextracttheindividualframesbasedonthecompressionalgorithm.Incontrasttothis,ontheWeb,JavaScript,andCSSareinchargeofproducingframes,renderedbythebrowser’srenderingengine.
Inthecontextofthebrowser,wecanthinkofeachframeasasnapshotofthewebpageatagivenmoment.Thedifferentframesarerenderedfastoneafteranother,sointheory,theenduseroftheapplicationshouldseethemsmoothlyincorporatedtogether,justlikeavideoplayedinavideoplayer.
OntheWeb,wearetryingtoreach60fps(framespersecond),whichmeansthateachframehasabout16mstobecomputedandrenderedonthescreen.Thisdurationincludesthetimerequiredbythebrowsertomakeallthenecessarycalculationsforthelayoutandtherenderingofthepage,andalsothetimethatourJavaScriptneedstoexecute.
Intheend,wehavelessthan16ms(becauseofthebrowserrenderingfunctionalitythattakestimedependingonthecalculationsitneedstoperform)forourJavaScripttofinishexecution.Ifitdoesn’tfitinthisduration,theframeratewilldropbyhalf.SinceJavaScriptisasingle-threadedlanguage,allthecalculationsneedtohappeninthemainUIthreadthat,inthecaseofcomputationally-intensiveapplications(suchasimageorvideoprocessing,marshalingandunmarshalingbigJSONstrings,andsoon),canleadtoverypooruserexperiencebecauseoftheframesbeingdropped.
HTML5introducedanAPIcalledWebWorkers,whichallowstheexecutionofclient-sidecodeinthebrowserenvironmentintomultiplethreads.Forsimplicity,thestandarddoesn’tallowsharedmemorybetweenindividualthreads,butinsteadallowscommunicationwithmessagepassing.ThemessagesexchangedbetweenWebWorkersandthemainUIthreadmustbestrings,whichoftenrequirestheserializationanddeserializationofJSONstrings.
Thelackofsharedmemorybetweentheindividualworkers,andtheworkersandthemainUIthreadbringsacoupleoflimitations,suchas:
DisabledaccesstotheDOMbytheworkerthreads.Globalvariablescannotbesharedamongtheindividualcomputationalunits(thatis,workerthreadsandmainUIthreadsandviceversa).
WebWorkersandAngular2BecauseoftheplatformagnosticdesignofAngular2,thecoreteamdecidedtotakeadvantageofthisAPI,andduringthesummerof2015,GoogleembeddedWebWorkerssupportintotheframework.ThisfeatureallowsmostoftheAngular2applicationstoberunonaseparatethread,makingthemainUIthreadresponsibleonlyforrendering.Thishelpsusachievethegoalof60fpsmucheasilythanrunningtheentireapplicationinasinglethread.
TheWebWorkerssupportisnotenabledbydefault.Whenenablingit,weneedtokeepsomethinginmind—inaWebWorkers-readyapplication,thecomponentsarenotgoingtoberuninthemainUIthread,whichdoesnotallowustodirectlymanipulatetheDOM.Inthiscase,weneedtousebindings,suchasinputs,outputs,andacombinationofbothwithNgModel.
BootstrappinganapplicationrunninginWebWorkerLet’smaketheto-doapplicationthatwedevelopedinChapter4,GettingStartedwithAngular2ComponentsandDirectivesworkinWebWorkers.Youcanfindtheexamplethatwe’llexploreatch8/ts/todo_webworkers/.
Firstofall,let’sdiscussthechangesthatweneedtomake.Takealookatch4/ts/inputs-outputs/app.ts.Noticethatinsideofapp.ts,weincludedthebootstrapfunctionfromtheangular2/platform/browsermodule.Thisisthefirstthingweneedtomodify!Thebootstrapprocessofanapplicationrunninginabackgroundprocessisdifferent.
Beforerefactoringourcode,let’stakealookatadiagramthatillustratesthebootstrapprocessofatypicalAngular2applicationrunninginWebWorkers:
JasonTeplitz,whoimplementedtheWebWorkersupportinAngular2,presentedthisdiagramduringhistalkonAngularConnect2015.
Thediagramhastwoparts:UIandWebWorker.UIshowstheactionsperformedduringinitializationinthemainUIthread;theWebWorkerpartofthediagramshowshowtheapplicationgetsbootstrappedinthebackgroundthread.Now,let’sexplainthebootstrapprocessstepbystep.
First,theuseropenstheindex.htmlpage,whichtriggersthedownloadofthefollowingtwofiles:
TheUIbundleofAngular2usedforapplicationsrunninginWebWorker.Thesystem.jsbundle(wetalkedabouttheglobalobjectSysteminChapter3,TypeScriptCrashCourse.Wecanthinkofthesystem.jsbundleasapolyfillforthemoduleloader).
Usingsystem.js,wedownloadthescriptusedfortheinitializationofthepartoftheapplicationrunninginthemainUIthread.Thisscriptstartsloader.jsinWebWorker.Thisisthefirstscriptthatisrunninginabackgroundthread.Oncetheworkerisstarted,loader.jswilldownloadsystem.jsandthebundleofAngular2,whichismeanttoberuninthebackgroundthread.Thefirstrequestwillusuallyhitthecachebecausesystem.jsisalreadyrequestedbythemainthread.Usingthemoduleloader,wedownloadthescriptthatisresponsibleforbootstrappingthebackgroundappbackground_bootstrap.js,whichwillfinallystartthefunctionalityofourapplicationinthebackground.
Fromnowon,theentireapplicationthatwebuiltwillberuninWebWorkerandwillexchangemessageswiththemainUIthreadforrespondingtousereventsandrenderinginstructions.
Nowthatweareawareofthebasicflowofeventsduringinitializationwhenusingworkers,let’srefactorourto-doapplicationtotakeadvantageofthem.
MigratinganapplicationtoWebWorkerInsideofindex.html,weneedtoaddthefollowingscripts:
<!--ch8/ts/todo_webworkers/index.html-->
…
<scriptsrc="/node_modules/systemjs/dist/system.src.js">
</script>
<scriptsrc="/node_modules/angular2/bundles/angular2-polyfills.js">
</script>
<scriptsrc="/node_modules/angular2/bundles/web_worker/ui.dev.js">
</script>
<script>
System.config({
baseURL:'/dist/dev/ch8/ts/todo_webworkers/'
});
System.import('./bootstrap.js')
.catch(function(){
console.log('Reportthiserrorto
https://github.com/mgechev/switching-to-angular2/issues',e);
});
</script>
…
Intheprecedingsnippet,we’veincludedreferencestosystem.js,angular2-polyfillsthatincludeszone.jsandtheothersusedbyAngularlibraries,andui.dev.jswhichisthebundlethatneedstoberuninthemainUIthread.
Rightafterthis,wewillconfiguresystem.jsbysettingthebaseURLpropertyofthemoduleloader.Forthenextstep,wewillexplicitlyimportthebootstrap.jsfile,whichcontainsthelogicusedforstartingtheloader.jsscriptinWebWorker.
Let’sexplorebootstrap.js,whichistheoriginalofthetranspiledbootstrap.js:
//ch8/ts/todo_webworkers/bootstrap.ts
import{platform,Provider}from'angular2/core';
import{
WORKER_RENDER_APPLICATION,
WORKER_RENDER_PLATFORM,
WORKER_SCRIPT
}from'angular2/platform/worker_render';
platform([WORKER_RENDER_PLATFORM])
.application([WORKER_RENDER_APPLICATION,
newProvider(WORKER_SCRIPT,{useValue:'loader.js'})]);
Inthisfile,wesettheplatformtothetypeWORKER_RENDER_PLATFORMandtheapplicationtypetoWORKER_RENDER_APPLICATION.WeconfiguredtheproviderusedforinjectingtheWORKER_SCRIPTtokentousethevalue'loader.js'.Aswesaid,loader.jsisgoingtoruninabackgroundthread.Thescriptislocatedintheapplication’sroot.
Now,wecanmovetotherightofthediagramgivenintheBootstrappinganapplicationrunninginaWebWorkersection.Thelogicinloader.jsisquitesimple:
//ch8/ts/todo_webworkers/loader.ts
importScripts("/node_modules/systemjs/dist/system.src.js",
"/node_modules/angular2/bundles/web_worker/worker.dev.js",
"/node_modules/angular2/bundles/angular2-polyfills.js");
System.config({
baseURL:'/dist/dev/ch8/ts/todo_webworkers/',
});
System.import('./background_app.js')
.then(()=>console.log('Theapplicationhasstartedsuccessfully'),
error=>console.error('Errorloadingbackground',error));
Asthefirststep,weimportsystem.js,theWebWorkersbundleofAngular2(worker.dev.js),andalltherequiredpolyfills.Then,weconfigurethebackgroundinstanceofthemoduleloaderandimportthebackground_appfile,whichcontainsthelogicofourapplicationaswellastheWebWorkersbootstrapcall.
Now,let’sexplorehowwebootstraptheapplicationinsideWebWorker:
import{platform}from'angular2/core';
import{
WORKER_APP_PLATFORM,
WORKER_APP_APPLICATION
}from'angular2/platform/worker_app';
//Logicfortheapplication…
platform([WORKER_APP_PLATFORM])
.application([WORKER_APP_APPLICATION])
.bootstrap(TodoApp);
JustlikeinthebootstrapinthemainUIthread,wespecifythetypeoftheplatformandthetypeoftheapplicationthatwewanttobootstrap.Inthefinalstep,wesettherootcomponentjustlikewedidinthestandardbootstrapprocess.TheTodoAppcomponentisdefinedbetweentheimportsandtheinitializationcallsinthebackground_appfile.
MakinganapplicationcompatiblewithWebWorkersAswesaid,thecodethatrunsinthecontextofWebWorkerdoesnothaveaccesstotheDOM.Let’sseewhatchangesweneedtomakeinordertoaddressthislimitation.
ThisistheoriginalimplementationoftheInputBoxcomponent:
//ch4/ts/inputs-outputs/app.ts
@Component({
selector:'input-box',
template:`
<input#todoInput[placeholder]="inputPlaceholder">
<button(click)="emitText(todoInput.value);
todoInput.value='';">
{{buttonLabel}}
</button>
`
})
classInputBox{
@Input()inputPlaceholder:string;
@Input()buttonLabel:string;
@Output()inputText=newEventEmitter<string>();
emitText(text:string){
this.inputText.emit(text);
}
}
Noticethatinsidethetemplate,wenamedtheinputelementtodoInputanduseditsreferencewithintheexpressionsetasthehandleroftheclickevent.ThiscodewillnotbeabletoruninWebWorker,sincewedirectlyaccessaDOMelementinsidethetemplate.Inordertotakecareofthis,weneedtorefactorthesnippet,soitusesAngular2bindingsinsteadofdirectlytouchinganyelements.WecaneitheruseinputswhenasingledirectionbindingmakessenseorNgModelforachievingtwo-waydata-binding,whichismorecomputationally-intensive.
Let’suseNgModel:
//ch8/ts/todo_webworkers/background_app.ts
import{NgModel}from'angular2/common';
@Component({
selector:'input-box',
template:`
<input[placeholder]="inputPlaceholder"[(ngModel)]="input">
<button(click)="emitText()">
{{buttonLabel}}
</button>
`
})
classInputBox{
@Input()inputPlaceholder:string;
@Input()buttonLabel:string;
@Output()inputText=newEventEmitter<string>();
input:string;
emitText(){
this.inputText.emit(this.input);
this.input='';
}
}
InthisversionoftheInputBoxcomponent,wewillcreateatwo-waydata-bindingbetweentheinputelementandtheinputpropertyoftheInputBoxcomponent.Oncetheuserclicksonthebutton,theemitTextmethodwillbeinvoked,whichwilltriggeraneweventemittedbyinputTextEventEmitter.Inordertoresetthevalueoftheinputelement,wetakeadvantageofthetwo-waydata-bindingthatwedeclaredandsetthevalueoftheinputpropertytotheemptystring.
NoteMovingtheentirelogicfromthetemplatesofthecomponentstotheircontrollersbringsalotofbenefits,suchasimprovedtestability,maintainability,codereuse,andclarity.
TheprecedingcodeiscompatiblewiththeWebWorkersenvironment,sincetheNgModeldirectiveisbasedonanabstractionthatdoesnotmanipulatetheDOMdirectly,butinstead,underthehood,exchangesmessagesasynchronouslywiththemainUIthread.
Torecap,wecansaythatwhilerunningapplicationsinthecontextofWebWorkers,weneedtokeepthefollowingtwothingsinmind:
Weneedtouseadifferentbootstrapprocess.WeshouldnotaccesstheDOMdirectly.
Typicalscenariosthatviolatethesecondpointareasfollows:
ChangingtheDOMofthepagebyselectinganelementandmanipulatingitdirectlywiththebrowser’snativeAPIsorathird-partylibrary.AccessingnativeelementsinjectedbyusingElementRef.Creatingareferencetoanelementinthetemplateandpassingitasanargumenttomethods.Directlymanipulatinganelementreferencedwithinthetemplate.
Inallthesescenarios,weneedtousetheAPIsprovidedbyAngular.Ifwebuildourapplicationsaccordingtothispractice,wewillbenefitnotonlyfrombeingabletoruntheminWebWorkers,butalsofromincreasingthecodereuseincasewewanttousethemacrossdifferentplatforms.
Keepingthisinmindwillallowustotakeadvantageofserver-siderendering.
Initialloadofasingle-pageapplicationInthissection,wewillexplorewhatserver-siderenderingis,whyweneeditinourapplications,andhowwecanuseitwithAngular2.
Forourpurposes,we’llexplainthetypicalflowofeventswhenauseropensaSPAimplementedinAngular2.First,we’lltracetheeventswiththeserver-siderenderingdisabled,andafterthat,we’llseehowwecanbenefitfromthisfeaturebyenablingit.OurexamplewillbeillustratedinthecontextofHTTP1.1.
Thisimageshowsthefirstrequestbythebrowserandthecorrespondingserver’sresponsewhenloadingatypicalSPA.TheresultthattheclientwillseeinitiallyistheinitialcontentoftheHTMLpagewithoutanyrenderedcomponents.
Let’ssupposethatwedeploytheto-doapplicationwebuiltinChapter4,GettingStartedwithAngular2ComponentsandDirectivestoawebserverthathasthehttps://example.comdomainassociatedwithit.
Oncetheusernavigatestohttps://example.com/,thebrowserwillopenanewHTTPGETrequest,fetchingtherootresource(/).Whentheserverreceivestherequest,itwillrespondwithanHTMLfilethat,inourcase,willlooksomethinglikethis:
<!DOCTYPEhtml>
<htmllang="en">
<head>
<title>SwitchingtoAngular2</title>
<linkrel="stylesheet"href="bootstrap.min.css">
</head>
<body>
<app>Loading…</app>
<scriptsrc="es6-shim.min.js"></script>
<scriptsrc="Reflect.js"></script>
<scriptsrc="system.src.js"></script>
<scriptsrc="angular2-polyfills.js"></script>
<scriptsrc="Rx.min.js"></script>
<scriptsrc="angular2.js"></script>
<scriptsrc="router.js"></script>
<scriptsrc="http.min.js"></script>
<script>…</script>
</body>
</html>
Thebrowserwillreceivethiscontentasthebodyoftheresponse.Whenthemarkupisrenderedontothescreen,allthattheuserwillseeisthelabel:Loading….
Inthenextstep,thebrowserwillfindallthereferencesintheHTMLfile’sexternalresources,suchasstylesandscripts,andstartdownloadingthem.Inourcase,someofthemarebootstrap.css,es6-shim.min.js,Reflect.js,system.src.js,andangular2-polyfills.js.
Onceallthereferencedresourcesareavailable,therestillwon’tbeanysignificantvisualprogressfortheuser(exceptifthestylesfromthedownloadedCSSfileareappliedtothepage).Thiswon’tchangeuntiltheJavaScriptvirtualmachineprocessesallthereferencedscriptsrelatedtotheapplication’simplementation.Atthispoint,AngularwillknowwhichcomponentneedstoberenderedbasedonthecurrentURLandbootstrap’sconfiguration.
Ifthecomponentassociatedwiththepageisdefinedinaseparatefileoutsideofourmainapplicationbundle,theframeworkwillneedtodownloadittogetherwithitsentiredependencygraph.Incasethetemplateandthestylesofthecomponentareexternalized,Angularwillneedtodownloadthemaswellbeforeitisabletorendertherequestedpage.
Rightafterthis,theframeworkwillbeabletocompilethetemplateassociatedwiththetargetcomponentandrenderthepage.
Inthepreviousscenario,therearethefollowingtwomainpitfalls:
SearchenginesarenotthatgoodatindexingdynamiccontentgeneratedbyJavaScript.ThismeansthattheSEO(SearchEngineOptimization)ofourSPAwillsuffer.Incaseoflargeapplicationsand/orpoorInternetconnection,theuserexperiencewillbepoor.
Inthepast,wesolvedtheSEOissueintheapplicationsbuiltwithAngularJS1.xwithdifferentworkarounds,suchasusingheadlessbrowserforrenderingtherequestedpage,cachingitontothedisk,andlaterprovidingittosearchengines.However,there’samoreelegantsolution.
InitialloadofaSPAwithserver-siderenderingAcoupleofyearsago,librariessuchasRendr,Derby,Meteor,andtheothersintroducedtheconceptofisomorphicJavaScriptapplications,whichwerelaterrenamedtouniversal.Inessence,universalapplicationscouldberunontheclientaswellasontheserver.SuchportabilityisonlypossibleinthecaseoflowcouplingbetweentheSPAandthebrowser’sAPIs.Thegreatestbenefitofthisparadigmisthattheapplicationcanbererenderedontheserverandlatersenttotheclient.
Universalapplicationsarenotframework-specific;wecantakeadvantageoftheminanyframeworkthatcanberunoutsideoftheenvironmentofthebrowser.Conceptually,thepracticeofserver-siderenderingisverysimilaracrossplatformsandlibraries;onlyitsimplementationdetailsmaydiffer.Forinstance,theAngular2Universalmodule,whichimplementsserver-siderendering,hassupportfornode.jsaswellasASP.NETthat,atthemomentofthiswriting,isstillworkinprogress.
TheprecedingimageshowstheresponsebytheservertotheinitialbrowserGETrequest.Thistime,incontrasttothetypicalscenarioofloadingaSPA,thebrowserwillgettherenderedcontentoftheHTMLpage.
Let’stracetheflowoftheeventsinthesameapplicationwiththeserver-siderenderingfeatureenabled.Inthiscase,oncetheserverreceivestheHTTPGETrequestbythebrowser,itwillruntheSPAontheserverinthenode.jsenvironment.AlltheDOMcallsaregoingtoberedirectedtoaserver-sideDOMimplementationandbeexecutedinthecontextoftheusedplatform.Similarly,alltheAJAXcallswiththehttpmodulewillbehandledbytheserver-sideimplementationofthemodule.Thisway,theapplicationwillnotmakeanydifference,whetheritisrunninginthecontextofthebrowserortheserver.
OncetherenderedversionoftheSPAisavailable,itcanbeserializedtoHTMLandsenttothebrowser.Thistime,duringtheapplication’sinitialization,insteadoftheLoading…label,theuserwillseethepagetheyrequestedrightaway.
Notethatatthispoint,theclientwillhavetherenderedversionoftheapplication,butall
thereferencedexternalresources,suchasscriptsandstyles,stillneedtobeavailable.Thismeansthat,initially,noneoftheCSSstylesdeclaredintheexternalfileswillbeappliedandtheapplicationwillnotberesponsivetoanyuser-relatedinteractions,suchasthemouseandkeyboardevents.
NoteNotethatincasethescriptsareinlinedintotheserver-siderenderedpage,theapplicationwillberesponsivetouserevents.However,inliningbigchunksofJavaScriptisgenerallyconsideredasabadpractice,sinceitwillincreasethepage’ssizedramaticallyandpreventthescriptsfromcaching.Bothwillinfluencethenetworkperformance.
WhentheJavaScriptvirtualmachineprocessestheJavaScriptassociatedwiththepage,ourSPAwillbereadytouse.
Server-siderenderingwithAngular2Inthefirsthalfof2015,PatrickStapletonandJeffWhelpleyannouncedthattheystartedthedevelopmentofthemodule,Universal.Universalisalibrarythatallowsustobuilduniversal(alsocalledisomorphic)JavaScriptapplicationswithAngular2;inotherwords,itprovidesserver-siderenderingsupport.
ApplicationsbuiltwithAngular2andUniversalwillnotberesponsiveuntilalltheJavaScriptbelongingtotherequestedpageisprocessed.Thisisadrawbackthatwealreadymentioned,whichisvalidforalltheserver-siderenderedapplications.However,PatrickandJeffintroducedpreboot.js,whichisalightweightlibrarythatwillbeinlinedonthepagerenderedbytheserverandavailableaftertheinitialclientrequest.
Preboot.jshasseveralstrategiesforthemanagementofthereceivedclienteventsbeforetheapplicationhasbeencompletelyinitialized.Theyareasfollows:
Recordandplaybackevents.Respondimmediatelytoevents.Maintainfocuswhenapageisrerendered.Bufferclient-sidere-renderingforsmoothertransition.Freezepageuntilthebootstrapiscompleteifauserclicksonabutton.
Atthemomentofthiswriting,theUniversalmoduleisstillbeingactivelydeveloped.However,youcangiveitatryusingtheAngular2universalstarterathttps://github.com/angular/universal-starter.
EnhancingourdevelopmentexperienceOurexperienceasdeveloperscanbeenhancedintermsofproductivityorbyallowingustohavemorefunwhileworkingonourprojects.Thiscanbeachievedwithallthetools,IDEs,texteditors,andmorethatweuseonadailybasis.Inthissection,we’llbrieflytakealookatpopularIDEsandtexteditorsthatwecanusefortakingadvantageofthestaticcodeanalysisfeaturesthatAngular2provides.
Inthesecondpartofthissection,we’llseewhathotreloadingisandhowwecantakeadvantageofitduringthedevelopmentofAngular2applications.
TexteditorsandIDEsAswealreadymentionedatthebeginningofthebook,thecoreteamputgreateffortintoenhancingthetoolingsupportinAngular2.Firstofall,theframeworkisbuiltwithTypeScript,whichnaturallyallowsustousestatictypingduringourdevelopmentprocess.SomeofthetexteditorsandIDEsthathavegreatTypeScriptsupportareasfollows:
IntelliJIdea:Ageneral-purposeIDEbyJetBrains.WebStorm:AnIDEspecializedforwebdevelopmentbyJetBrains.VSCode:Across-platformtexteditorwritteninTypeScriptanddevelopedbyMicrosoft.SublimeText:Across-platformtexteditor.Atom:Across-platformtexteditor.
Recently,JetBrainsannouncedadvancedAngular2supportinIntelliJIdeaandWebStorm,whichsupportsautocompletionforcomponentsandbindings.
AlthoughnotallthementionedIDEsandtexteditorshaveAngular2-specificsupportatthemomentofthiswriting,Angular2comeswithagreatdesign.Itallowsustoperformadvancedstaticcodeanalysisontheapplication’scodebaseforthedevelopmentofsophisticatedrefactoringandproductivitytoolsinthenearfuture.Untilthen,Angular2atleastprovidestoolingsupportasgoodanyotherJavaScriptframeworkinthemarket.
HotreloadingHotreloading(orhotloading)isapracticethatgotpopularintheworldofpurelyfunctionaluserinterfacesinlibrariessuchasOm(usedwithClojureScript)andReact.
WhendevelopingaSPA,itisquiteannoyingtorefreshyourbrowseraftereachsmallchangeofastyle,view,orevenacomponent.That’swhyacoupleofyearsago,atoolwasdevelopedcalledlivereload.Livereloadwatchesthefilesofourapplication,andwhenitdetectsachangeinanyofthem,itsendsamessagetothebrowsertorefreshthepage.Usually,theconnectionestablishedbetweenthelivereloadserverandtheclientisthroughWebSockets,sincetheserverneedstosendpushnotifications.Althoughthistoolworksgreatinsomecases,ithasonebigdisadvantage:oncethepageisrefreshed,allofthestatecollectedduringthedeveloper’sinteractionwillbelost.
Forinstance,imagineascenariowhereyou’reworkingonanapplicationwithacomplexview.Younavigatethroughafewpages,fillinforms,andsetthevaluestoinputfields,andthen,unexpectedly,youfindanissue.YougotoyourtexteditororIDEandfixtheissue;thelivereloadserverdetectsachangeinyourproject’srootandsendsanotificationtothebrowserinordertorefreshthepage.Now,you’rebacktotheinitialstateoftheapplicationandyouneedtogothroughallthesestepsinordertoreachthesamepointbeforetherefresh.
Incontrasttolivereloading,inmostcases,hotreloadingcaneliminatethestatelost.Let’stakeabrieflookathowitworks.
Atypicalimplementationofahotreloaderhastwomainmodules:aclientandaserver.Incontrasttotheserverinlivereloading,thehotreloaderservernotonlywatchesthefilesystemforchanges,butalsotakesthecontentofthechangedfileandsendsittothebrowser.Oncethebrowserreceivesthemessagesentbytheserver,itcanswapthepreviousimplementationofthechangedunitwiththenewone.Afterthis,theviewaffectedbythechangecanbererenderedinordertovisuallyreflectthechange.Sincetheapplicationdoesn’tloseitsstate,wecancontinuefromthepointwe’vereachedwiththenewversionofthechangedcodeunit.
Unfortunately,itisnotalwayspossibletodynamicallyswaptheimplementationsofallyourcomponentsusingthisstrategy.Ifyouupdateapieceofcodethatholdsthatholdsapplicationstate,youmayneedtorefreshthepagemanually.
HotreloadinginAngular2Atthetimeofwriting,thereisaworkingprototypeofAngular2hotreloaderthatcanbetestedwiththeangular2-seeddescribedintheAngular2quickstarterssection.Theprojectisinactivedevelopment,sotherearealotofimprovementsontheroadmap.Butitalreadyprovidesitscorefunctionality,whichcaneasethedevelopmentexperiencesignificantly.
Bootstrappingaprojectwithangular-cliDuringAngularConnect2015,BradGreenandIgorMinar,partoftheAngularteam,announcedangular-cli—aCLI(command-lineinterface)tooltoeasestartingandmanagingAngular2applications.ForthosewhohaveusedRubyonRails,theideabehindtheCLItoolmightbefamiliar.Thebasicpurposeofthetoolistoallowthequickbootstrappingofnewprojectsandscaffoldingofnewdirectives,components,pipes,andservices.
Atthetimeofwriting,thetoolisstillintheearlystageofdevelopment,sowe’lldemonstrateonlyitsbasicusage.
Usingangular-cliInordertoinstalltheCLItool,runthefollowingcommandinyourterminal:
npminstall-gangular-cli
Rightafterthis,theglobalngcommandwillappearinyour$PATH.ForcreatinganewAngular2project,usethefollowing:
#Maytakeawhile,dependingonyourInternetconnection
ngnewangular-cli-project
cdangular-cliproject
ngserve
Theprecedingcommandswilldothefollowing:
CreateanewAngular2projectandinstallallofitsnode.jsdependencies.Enteryourproject’sdirectory.Startadevelopmentwebserverthatwillletyouopentheapplicationyoujustcreatedinyourwebbrowser.
Forfurtherreading,takealookattheproject’srepositorylocatedathttps://github.com/angular/angular-cli.
Angular2quickstartersAlthoughAngular2CLIisgoingtobeamazing,atthemomentofthiswriting,itisstillataveryearlystageofdevelopment.It’sbuild-toolagnostic,whichmeansthatitdoesn’tprovideanybuildsystem.Luckily,therearealotofstarterprojectsdevelopedbythecommunitythatcanprovideagreatstartingpointforournextAngular2project.
Angular2seedIncaseyouenjoyGulpandstatictyping,youcangiveatrytotheangular2-seedproject.ItishostedonGitHubatthefollowingURL:https://github.com/mgechev/angular2-seed.
TheAngular2seedprovidesthefollowingkeyfeatures:
Advanced,ready-to-go,easy-to-extend,modular,andstaticallytypedbuildsystemusingGulp.Productionanddevelopmentbuilds.SampleunittestswithJasmineandKarma.End-to-endtestswithProtractor.AdevelopmentserverwithLivereload.Experimentalhotreloadingsupport.Followingthebestpracticesforyourapplications’andfiles’organization.ManagerfortheTypeScript-relatedtypedefinitions.
Thecodedistributedwiththebookisbasedonthisseedproject.
Forangular2-seed,youneedtohavenode.js,npm,andGitinstalled,andyouneedtorunthefollowinglistofcommands:
gitclone--depth1https://github.com/mgechev/angular2-seed.git
cdangular2-seed
npminstall
npmstart
Afteryourunthesecommands,yourbrowserwillbeautomaticallyopenedwiththehomepageoftheseed.OnthechangeofanyoftheTypeScriptfiles,thecodewillbeautomaticallytranspiledtoJavaScriptandyourbrowserwillberefreshed.
Theproductionbuildisconfigurable,butbydefault,itproducesasinglebundlethatcontainsaminifiedversionoftheapplicationandallthereferencedlibraries.
Angular2WebpackstarterIfyoupreferdeclarativeandminimalisticbuildswithWebpack,youcanuseangular2-webpack-starter.ItisastarterprojectdevelopedbyAngularClassandhostedonGitHub.YoucanfinditatthefollowingURL:https://github.com/AngularClass/angular2-webpack-starter.
Thisstarterprovidesthefollowingfeatures:
ThebestpracticesinfileandapplicationorganizationforAngular2.Ready-to-gobuildsystemusingWebpackforworkingwithTypeScript.TestingAngular2codewithJasmineandKarma.CoveragewithIstanbulandKarma.End-to-endAngular2codeusingProtractor.TypemanagerwithTypings.
Inordertogiveitatry,youneedtohavenode.js,npm,andgitinstalled,andyouneedtorunthefollowingcommands:
gitclone--depth1https://github.com/angularclass/angular2-webpack-
starter.git
cdangular2-webpack-starter
npminstall
./node_modules/.bin/typingsinstall
npmstart
SummaryWestartedthisbookbyintroducingthereasonsbehindthedevelopmentofAngular2,whichwasfollowedbyaconceptualoverviewthatgaveusageneralideaaboutthebuildingblocksthattheframeworkprovidesforapplicationdevelopment.Inthenextstep,wedidaTypeScriptcrashcoursethatpreparedusforChapter4,GettingStartedwithAngular2ComponentsandDirectiveswherewewentdeepintoAngular’sdirectives,components,andchangedetection.
InChapter5,DependencyInjectioninAngular2weexplainedthedependencyinjectionmechanismandsawhowwecanmanagetherelationsbetweenthedifferentcomponentsbyusingit.Thenextchaptersexplainedtoushowwecanbuildformsandpipes,andtakeadvantageofAngular2’scomponent-basedrouter.
Bycompletingthecurrentchapter,wefinishedourjourneyintotheframework.Atthemomentofthiswriting,thedesigndecisionsandtheideasbehindAngular2’scorearesolidandfinalized.Althoughtheframeworkisstillbrandnew,inthepastcoupleofmonthsitsecosystemreachedalevelthatwecandevelopproduction-ready,high-performance,SEO-friendlyapplications,andontopofthis,haveagreatdevelopmentexperienceexploitingstatictypingandhotreloading.
IndexA
accessmodifierspublic/Usingaccessmodifiersprivate/Usingaccessmodifiersprotected/Usingaccessmodifiers
ambienttypedefinitionsusing/Usingambienttypedefinitionspredefinedambienttypedefinitions,using/Usingambienttypedefinitions,Customambienttypedefinitionscustom/Customambienttypedefinitionsts.dfiles,defining/Definingts.dfiles
AMD(AsynchronousModuleDefinition)/WritingmodularcodewithES2015angular-cli
used,forbootstrappingproject/Bootstrappingaprojectwithangular-cliusing/Usingangular-cliURL/Usingangular-cli
Angular2conceptualoverview/AconceptualoverviewofAngular2components/ComponentsinAngular2route,definitionsyntax/Angular2routedefinitionsyntaxHelloworld!application,building/TheHelloworld!applicationinAngular2playingwith/PlayingwithAngular2andTypeScriptDependencyInjection(DI)/DependencyInjectioninAngular2model-drivenforms,developing/Developingmodel-drivenformsinAngular2HTTPmodule,exploring/ExploringtheHTTPmoduleofAngularHTTPmodule,using/UsingAngular’sHTTPmodulebuilt-inpipes,using/UsingAngular’sbuilt-inpipesAsyncPipe,using/UsingAngular’sAsyncPipeandWebWorkers/WebWorkersandAngular2hotreloading/HotreloadinginAngular2about/Angular2quickstartersGulpseed/Angular2seedWebpackstarter/Angular2Webpackstarter
Angular2andTypeScriptindex,defining/Diggingintotheindex
Angular2directivesusing/UsingAngular2directivesngFordirective/ThengFordirectivedefining/DefiningAngular2directivesinputs,setting/Settingthedirective’sinputsconstructor,defining/Understandingthedirective’sconstructor
encapsulation/BetterencapsulationofdirectivesAngular2forms
using/UsingAngular2formsmodel-drivenapproach/UsingAngular2formstemplate-drivenforms,developing/Developingtemplate-drivenformstemplate-drivenform’smarkup,exploring/Diggingintothetemplate-drivenform’smarkupbuilt-informvalidators,using/Usingthebuilt-informvalidatorscustomcontrolvalidators,defining/Definingcustomcontrolvalidatorsselectinputs,using/UsingselectinputswithAngularNgFormdirective,using/UsingtheNgFormdirectiveused,fortwo-waydata-binding/Two-waydata-bindingwithAngular2data,storing/Storingtheformdata
Angular2routerexploring/ExploringtheAngular2routerrootcomponent,defining/Definingtherootcomponentandbootstrappingtheapplicationapplication,bootstrapping/DefiningtherootcomponentandbootstrappingtheapplicationPathLocationStrategy,using/UsingPathLocationStrategyroutes,configuringwith@RouteConfig/Configuringrouteswith@RouteConfigrouterLink,using/UsingrouterLinkandrouter-outletrouter-outlet,using/UsingrouterLinkandrouter-outletlazy-loading,withAsyncRoute/Lazy-loadingwithAsyncRoute
angular2-seedprojectURL/Angular2seed
AngularJS1.xabout/LessonslearnedfromAngularJS1.xinthewildcontrollers/Controllersscopeobject/Scopedependencyinjection(DI)/DependencyInjectionserver-siderendering/Server-siderenderingsingle-pageapplications/Applicationsthatscaletemplates/Templateschangedetection/Changedetection,AngularJS1.xchangedetectionchangedetection,enhancing/EnhancingAngularJS1.x’schangedetection
applicationrunninginWebWorkers,bootstrapping/BootstrappinganapplicationrunninginWebWorkermigrating,toWebWorker/MigratinganapplicationtoWebWorker
AsyncPipeusing/UsingAngular’sAsyncPipeusing,withobservables/UsingAsyncPipewithobservables
AsyncRouteused,forlazy-loading/Lazy-loadingwithAsyncRoute
Bblockscope
used,fordefiningvariables/Definingvariableswithblockscopebuilt-inchangedetection
dynamicchangedetection/ChangedetectionJITchangedetection/Changedetection
built-indirectives,Angular2using/UsingAngular2’sbuilt-indirectives
built-informvalidatorsusing/Usingthebuilt-informvalidatorsminlength/Usingthebuilt-informvalidatorsmaxlength/Usingthebuilt-informvalidators
built-inpipesusing/UsingAngular’sbuilt-inpipesCurrencyPipe/UsingAngular’sbuilt-inpipesDatePipe/UsingAngular’sbuilt-inpipesDecimalPipe/UsingAngular’sbuilt-inpipesJsonPipe/UsingAngular’sbuilt-inpipesLowerCasePipe/UsingAngular’sbuilt-inpipesUpperCasePipe/UsingAngular’sbuilt-inpipesPercentPipe/UsingAngular’sbuilt-inpipesSlicePipe/UsingAngular’sbuilt-inpipesAsyncPipe/UsingAngular’sbuilt-inpipes
Cchangedetection
about/Changedetection,AconceptualoverviewofAngular2,Changedetectionexample/ClassicalchangedetectioninAngularJS1.x/AngularJS1.xchangedetectioninzone.js/Inthezone.jssimplifieddata-flow/Simplifieddataflowenhancing,inAngularJS1.x/EnhancingAngularJS1.x’schangedetectiondefining/Understandingandenhancingthechangedetectionenhancing/Understandingandenhancingthechangedetectionorderofexecution/Theorderofexecutionofthechangedetectorsstrategies/Changedetectionstrategiesperformanceboosting,withimmutabledataandOnPush/PerformanceboostingwithimmutabledataandOnPushimmutabledatastructures,usinginAngular/UsingimmutabledatastructuresinAngular
childinjectorsabout/Childinjectorsandvisibilitydependencies,configuring/Configuringdependencieselementinjectors/Introducingtheelementinjectors
Codersrepositoryapplicationdeveloping/Developingthe“Codersrepository”applicationviews/Developingthe“Codersrepository”applicationbootstrapping/Definingtherootcomponentandbootstrappingtheapplication
CommonJS/WritingmodularcodewithES2015component-basedrouter
about/Understandingthenewcomponent-basedrouterAngular2route,definitionsyntax/Angular2routedefinitionsyntax
Componentclassabout/GettingtoknowAngular2components
componentsabout/AconceptualoverviewofAngular2,GettingtoknowAngular2componentscomposing/ComponentsinactioninAngular2/ComponentsinAngular2DependencyInjection(DI),using/UsingDIwithcomponentsanddirectivesDependencyInjection(DI),exploringwith/ExploringDIwithcomponents
Compositeclassabout/GettingtoknowAngular2components
containerabout/Configuringaninjector
contentchildren
about/Nestingcomponentscontentprojection,Angular2
defining/ExplainingAngular2’scontentprojectionabout/BasiccontentprojectioninAngular2multiplecontentchunks,projecting/Projectingmultiplecontentchunkscomponents,nesting/NestingcomponentsViewChildren,using/UsingViewChildrenandContentChildrenContentChildren,using/UsingViewChildrenandContentChildrenViewChild,versusContentChild/ViewChildversusContentChild
controllerassyntaxabout/Scope
controllersabout/Controllers
controllers,componentimplementing/Implementingthecomponent’scontrollers
controlvalidatorscomposition,using/Usingcompositionofcontrolvalidators
CreateRetrieveUpdateandDelete(CRUD)/Developingtemplate-drivenformsCSP(Content-Security-Policy)
about/UnderstandingandenhancingthechangedetectionCSSclasses
about/Diggingintothetemplate-drivenform’smarkupng-untouched/Diggingintothetemplate-drivenform’smarkupng-touched/Diggingintothetemplate-drivenform’smarkupng-pristine/Diggingintothetemplate-drivenform’smarkupng-dirty/Diggingintothetemplate-drivenform’smarkupng-valid/Diggingintothetemplate-drivenform’smarkupng-invalid/Diggingintothetemplate-drivenform’smarkup
customcontrolvalidatorsdefining/Definingcustomcontrolvalidators
Ddata
transforming,pipesused/Transformingdatawithpipesdecorators
URL/Meta-programmingwithES2016decoratorsDefinitelyTyped
URL/Usingambienttypedefinitionsdependencies,childinjectors
configuring/Configuringdependenciesselfdecorator,using/Usingthe@Selfdecoratorselfinjector,skipping/Skippingtheselfinjectoroptionaldependencies,using/Havingoptionaldependenciesmultiproviders,using/Usingmultiproviders
dependencyinjection(DI)about/DependencyInjection
DependencyInjection(DI)needfor/WhydoIneedDependencyInjection?inAngular2/DependencyInjectioninAngular2benefits/BenefitsofDIinAngular2using,withcomponentsanddirectives/UsingDIwithcomponentsanddirectivesexploring,withcomponents/ExploringDIwithcomponentsusing,withES5/UsingAngular’sDIwithES5
differsabout/AconceptualoverviewofAngular2
DImechanismabout/AconceptualoverviewofAngular2
directivesabout/AconceptualoverviewofAngular2modifying/ChangingdirectivesDependencyInjection(DI),using/UsingDIwithcomponentsanddirectives
directivessyntaxsemantics,defining/Improvedsemanticsofthedirectivessyntaxvariables,declaringinsidetemplate/Declaringvariablesinsideatemplatesyntaxsugarused,intemplates/Usingsyntaxsugarintemplates
Domain-SpecificLanguage(DSL)about/UsingAngular’sDIwithES5
DomainSpecificLanguage(DSL)about/Templates,Changingdirectives
dynamicchangedetectionabout/Changedetection
EECMAScript
evolution/TheevolutionofECMAScriptWebComponents/WebComponentsWebWorkers/WebWorkers
ECMAScript5(ES5)about/Changingdirectives
ECMAScript2015(ES2015)about/TheevolutionoftheWeb–timeforanewframework
elementinjectorsabout/Introducingtheelementinjectorsproviders,declaring/DeclaringprovidersfortheelementinjectorsDependencyInjection(DI),exploringwithcomponents/ExploringDIwithcomponentsviewProviders,versusproviders/viewProvidersversusproviders
enumtypesabout/TheEnumtypes
environment,Angular2settingup/Settingupourenvironmentreferences/Settingupourenvironmentprojectrepository,installing/InstallingourprojectrepositoryURL,forissues/Installingourprojectrepository
ES5DependencyInjection(DI),usingwith/UsingAngular’sDIwithES5
ES2015TypeScriptsyntax/TypeScriptsyntaxandfeaturesintroducedbyES2015andES2016TypeScriptfeatures/TypeScriptsyntaxandfeaturesintroducedbyES2015andES2016arrowfunctions/ES2015arrowfunctionsandES2016classes,using/UsingtheES2015andES2016classesmodularcode,writingwith/WritingmodularcodewithES2015modulesyntax,using/UsingtheES2015modulesyntaximplicitasynchronousbehavior/Takingadvantageoftheimplicitasynchronousbehavioraliases,using/Usingaliasesmoduleexports,importing/Importingallthemoduleexportsdefaultexports/Defaultexportsmoduleloader/ES2015moduleloaderandES2016,recap/ES2015andES2016recap
ES2016decoratorsmeta-programmingwith/Meta-programmingwithES2016decoratorsconfigurabledecorators,using/Usingconfigurabledecorators
Ffactories
using/Usingexistingprovidersdefining,forinstantiatingservices/Definingfactoriesforinstantiatingservices
forwardreferencesabout/Introducingforwardreferences
Ggenericcode
writing,typeparametersused/Writinggenericcodebyusingtypeparametersgenericfunctions,using/Usinggenericfunctionsmultipletypeparameters,having/Havingmultipletypeparameters
genericviewsdefining,withTemplateRef/DefininggenericviewswithTemplateRef
GitHubAPItokenreferencelink/ExploringtheHTTPmoduleofAngular
GoogleClosureCompiler/BettersupportbytexteditorsandIDEs
HHelloworld!application
defining,inAngular2/TheHelloworld!applicationinAngular2hostinjectors
about/Introducingtheelementinjectors,ExploringDIwithcomponentshotreloading
about/HotreloadinginAngular2/HotreloadinginAngular2
HTTPmoduleexploring/ExploringtheHTTPmoduleofAngularusing/UsingAngular’sHTTPmodule
IIDEs/TexteditorsandIDEsimplicitasynchronousbehavior,ES2015/Takingadvantageoftheimplicitasynchronousbehaviorinjector
about/Configuringaninjectorconfiguring/Configuringaninjectordependencyresolution,withgeneratedmetadata/Dependencyresolutionwithgeneratedmetadatainstantiating/Instantiatinganinjectorforwardreferences/Introducingforwardreferencesproviders,configuring/Configuringprovidersfactories,using/Usingexistingprovidersfactories,definingforinstantiatingservices/Definingfactoriesforinstantiatingserviceschildinjectors/Childinjectorsandvisibilityhierarchy,building/Buildingahierarchyofinjectors
inlinecachingreferencelink/Changedetection
interfacesabout/Defininginterfacesinheritance/Interfaceinheritancemultipleinterfaces,implementing/Implementingmultipleinterfaces
inversionofcontrol(IoC)about/DependencyInjection
isomorphic/Server-siderenderingwithAngular2
JJITchangedetection
about/Changedetection
LLeafclass
about/GettingtoknowAngular2componentslessverbosecode
writing,withTypeScriptstypeinference/WritinglessverbosecodewithTypeScript’stypeinferencebestcommontype/Bestcommontypecontextualtypeinference/Contextualtypeinference
lifecycle,componenthookinginto/Hookingintothecomponent’slifecycle
livereload/Hotreloading
MMassiveViewController(MVC)
about/Controllersmodel-drivenforms,Angular2
developing/Developingmodel-drivenformsinAngular2controlvalidatorscomposition,using/Usingcompositionofcontrolvalidators
Model-View-Controller(MVC)about/GettingtoknowAngular2components
Model-View-ViewModel(MVVM)about/GettingtoknowAngular2components
Model-View-Whatever(MVW)about/Changedetection
ModelViewController(MVC)about/Controllers
ModelViewPresenter(MVP)about/Controllers
ModelViewViewModel(MVVM)about/Controllers
ModelViewWhatever(MVW)about/Controllers
multiprovidersusing/Usingmultiproviders
Nn
URL/Settingupourenvironmentnestedroutes
defining/DefiningnestedroutesNgFormdirective
using/UsingtheNgFormdirectivenode.js
URL/UsingTypeScriptNodePackageManager(npm)/UsingTypeScript
used,forinstallingTypeScript/InstallingTypeScriptwithnpmnvm
URL/Settingupourenvironment
Oobject-oriented(OO)paradigm/UsingtheES2015andES2016classesObjecttypes
Arraytypes/TheArraytypesFunctiontypes/TheFunctiontypes
operationabout/GettingtoknowAngular2components
orderofexecutiontracing/Theorderofexecution
Pparameterizedviews
defining/DefiningparameterizedviewsPathLocationStrategy
using/UsingPathLocationStrategypipes
about/AconceptualoverviewofAngular2,Pipesdefining/Definingpipesused,fordatatransformations/Transformingdatawithpipesstatelesspipes,developing/Developingstatelesspipesbuilt-inpipes,using/UsingAngular’sbuilt-inpipesstatefulpipes,developing/Developingstatefulpipes
preboot.js/Server-siderenderingwithAngular2primitivetypes
about/UnderstandingthePrimitivetypesproviders
about/Configuringaninjectorconfiguring/Configuringprovidersexistingproviders,using/Usingexistingprovidersdeclaring,forelementinjectors/DeclaringprovidersfortheelementinjectorsversusviewProviders/viewProvidersversusproviders
R@RouteConfig
used,forrouteconfiguration/Configuringrouteswith@RouteConfigrootcomponent
defining/Definingtherootcomponentandbootstrappingtheapplicationroute
definitionsyntax/Angular2routedefinitionsyntaxrouter-outlet
using/UsingrouterLinkandrouter-outletrouterLink
using/UsingrouterLinkandrouter-outletRxJSGitHubrepository
about/TheHelloworld!applicationinAngular2URL/TheHelloworld!applicationinAngular2
Sscopeobject
about/ScopeSearchEngineOptimization(SEO)
about/Server-siderenderingselectinputs
using,withAngular2forms/UsingselectinputswithAngularselfdecorator
using/Usingthe@Selfdecoratorselfinjector
skipping/SkippingtheselfinjectorSEO(SearchEngineOptimization)
andUI/Initialloadofasingle-pageapplicationserver-siderendering
about/Server-siderenderingservices
about/AconceptualoverviewofAngular2,Understandingservicesinstantiating,withfactories/Definingfactoriesforinstantiatingservices
single-pageapplicationinitialload/Initialloadofasingle-pageapplicationinitialload,withenabledserver-siderendering/InitialloadofaSPAwithserver-siderenderingserver-siderendering,withAngular2/Server-siderenderingwithAngular2
single-pageapplicationsabout/Applicationsthatscale
single-pageapplications(SPA)about/Understandingthenewcomponent-basedrouter
statefulpipesdeveloping/Developingstatefulpipesusing/Usingstatefulpipes
statelesspipesdeveloping/Developingstatelesspipes
statictypingabout/Takingadvantageofstatictypingexplicittypedefinitions,using/Usingexplicittypedefinitionstypeany/Thetypeanyprimitivetypes/UnderstandingthePrimitivetypesenumtypes/TheEnumtypesObjecttypes/UnderstandingtheObjecttypesArraytypes/TheArraytypesFunctiontypes/TheFunctiontypesclasses,defining/Definingclassesaccessmodifiers,using/Usingaccessmodifiers
interfaces,defining/Defininginterfacesinterfaceinheritance/Interfaceinheritancemultipleinterfaces,implementing/Implementingmultipleinterfaces
storeddeveloperslisting/Listingallthestoreddevelopers
subtyping/Defininginterfaces
Ttemplate-drivenforms
developing/Developingtemplate-drivenformsmarkup,exploring/Diggingintothetemplate-drivenform’smarkup
TemplateRefgenericviews,definingwith/DefininggenericviewswithTemplateRef
templatesabout/Templates
texteditorsandIDEs/TexteditorsandIDEs
timetolive(TTL)about/Applicationsthatscale
Todoapplicationcomponents/Findingoutdirectives’inputsandoutputs
tokenabout/Configuringaninjector
transpilationabout/TheevolutionofECMAScript
TransportLayerSecurity(TLS)about/Definingfactoriesforinstantiatingservices
two-waydata-bindingusing,withAngular2/Two-waydata-bindingwithAngular2
typeinferenceused,forwritinglessverbosecode/WritinglessverbosecodewithTypeScript’stypeinference
typeparametersused,forwritinggenericcode/Writinggenericcodebyusingtypeparametersgenericfunctions,using/Usinggenericfunctionsmultipletypeparameters,having/Havingmultipletypeparameters
types,TypeScriptabout/Thetypeanyprimitivetypes/Thetypeanyuniontypes/Thetypeanyobjecttypes/Thetypeanytypeparameters/Thetypeany
TypeScriptabout/IntroductiontoTypeScriptcompile-timetypechecking/Compile-timetypecheckingtexteditorsandIDEs,support/BettersupportbytexteditorsandIDEsbenefits/There’sevenmoretoTypeScriptusing/UsingTypeScript,TheHelloworld!applicationinAngular2installing,npmused/InstallingTypeScriptwithnpmprogram,running/RunningourfirstTypeScriptprogram
syntax,byES2015andES2016/TypeScriptsyntaxandfeaturesintroducedbyES2015andES2016features,byES2015andES2016/TypeScriptsyntaxandfeaturesintroducedbyES2015andES2016ES2015arrowfunctions/ES2015arrowfunctionsES2015andES2016classes,using/UsingtheES2015andES2016classesvariables,definingwithblockscope/Definingvariableswithblockscopedecorators/FurtherexpressivenesswithTypeScriptdecoratorsplayingwith/PlayingwithAngular2andTypeScript
UUniversal/Server-siderenderingwithAngular2universalstarter
URL/Server-siderenderingwithAngular2useractions
handling/Handlinguseractionsdirectives’inputandoutput,using/Usingadirectives’inputsandoutputsdirectives’inputandoutput,findingout/Findingoutdirectives’inputsandoutputscomponent’sinputandoutput,defining/Definingthecomponent’sinputsandoutputsinput,passing/Passinginputsandconsumingtheoutputsoutput,consuming/Passinginputsandconsumingtheoutputseventbubbling/Eventbubblinginputandoutputofdirective,renaming/Renamingtheinputsandoutputsofadirectivealternativesyntax,fordefininginputandoutput/Analternativesyntaxtodefineinputsandoutputs
Vviewchildren
about/Nestingcomponentsviewencapsulation,component
defining/Introducingthecomponent’sviewencapsulationviewProviders
versusproviders/viewProvidersversusprovidersviews,Codersrepositoryapplication
basicdetails/Developingthe“Codersrepository”application
WWeb
evolution/TheevolutionoftheWeb–timeforanewframeworkWebComponents
about/TheevolutionoftheWeb–timeforanewframeworkevolution/WebComponents
WebpackstarterURL/Angular2Webpackstarter
WebWorkersapplications,running/RunningapplicationsinWebWorkersabout/RunningapplicationsinWebWorkersandAngular2/WebWorkersandAngular2application,bootstrapping/BootstrappinganapplicationrunninginWebWorkerandUI/BootstrappinganapplicationrunninginWebWorkerapplication,migratingto/MigratinganapplicationtoWebWorkercompatible,applicationcreating/MakinganapplicationcompatiblewithWebWorkers
WebWorkersabout/TheevolutionoftheWeb–timeforanewframework,WebWorkers
Zzone.js
changedetection/Inthezone.js