dl.ebooksworld.irdl.ebooksworld.ir/motoman/packt.learning.angular.2.pdf · improving productivity...
Post on 03-Jun-2020
7 Views
Preview:
TRANSCRIPT
www.EBooksWorld.ir
LearningAngular2
www.EBooksWorld.ir
TableofContents
LearningAngular2CreditsAbouttheAuthorAcknowledgmentsAbouttheReviewerwww.PacktPub.com
eBooks,discountoffers,andmoreWhysubscribe?
PrefaceWhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport
DownloadingtheexamplecodeErrataPiracyQuestions
1.CreatingOurVeryFirstComponentinAngular2Afreshstart
WebcomponentsWhyTypeScriptoverothersyntaxes?
SettingupourworkspaceInstallingdependenciesInstallingTypeScriptInstallingTypeScripttypings
Hello,Angular2!TypeScriptclassesIntroducingmetadatadecoratorsCompilingTypeScriptintobrowser-friendlyJavaScriptTheHTMLcontainerServingtheexamplesofthisbookPuttingeverythingtogether
EnhancingourIDESublimeText3AtomVisualStudioCodeWebStormLeveragingGulpwithotherIDEs
DivingdeeperintoAngular2components
www.EBooksWorld.ir
ImprovingproductivityComponentmethodsanddataupdatesAddinginteractivitytothecomponentImprovingthedataoutputintheviewandpolishingtheUI
Summary2.IntroducingTypeScript
UnderstandingthecaseforTypeScriptThebenefitsofTypeScriptIntroducingTypeScriptresourcesinthewild
TheTypeScriptofficialsiteTheTypeScriptWiki
TypesinTypeScriptString
DeclaringourvariablestheECMAScript6wayNumber
BooleanArray
DynamictypingwiththeanytypeEnum
VoidTypeinference
Functions,lambdas,andexecutionflowAnnotatingtypesinourfunctionsFunctionparametersinTypeScript
OptionalparametersDefaultparametersRestparametersOverloadingthefunctionsignature
BetterfunctionsyntaxandscopehandlingwithlambdasClasses,interfaces,andclassinheritance
Anatomyofaclass–constructors,properties,methods,getters,andsettersInterfacesinTypeScriptExtendingclasseswithclassinheritance
DecoratorsinTypeScriptClassdecorators
ExtendingtheclassdecoratorfunctionsignaturePropertydecoratorsMethoddecoratorsParameterdecorators
OrganizingourapplicationswithmodulesInternalmodulesExternalmodules
TheroadaheadSummary
www.EBooksWorld.ir
3.ImplementingPropertiesandEventsinOurComponentsAbettertemplatesyntax
DatabindingswithinputpropertiesSomeextrasyntacticsugarwhenbindingexpressionsEventbindingwithoutputpropertiesInputandoutputpropertiesinaction
SettingupcustomvaluesdeclarativelyCommunicatingbetweencomponentsthroughcustomevents
EmittingdatathroughcustomeventsLocalreferencesintemplatesAlternativesyntaxforinputandoutputproperties
ConfiguringourtemplatefromourcomponentclassInternalandexternaltemplatesEncapsulatingCSSstyling
ThestylespropertyThestyleUrlspropertyInlinestylesheets
ManagingviewencapsulationSummary
4.EnhancingOurComponentswithPipesandDirectivesDirectivesinAngular2
CoredirectivesNgIfNgForNgStyleNgClassNgSwitch,NgSwitchWhen,andNgSwitchDefault
ManipulatingtemplatebindingswithPipesTheuppercase/lowercasepipeThenumber,percent,andcurrencypipes
ThenumberpipeThepercentpipeThecurrencypipe
TheslicepipeThedatepipeTheJSONpipeThereplacepipeThei18npipes
Thei18nPluralpipeThei18nSelectpipe
TheasyncpipePuttingitalltogetherinthePomodorotasklist
SettingupourmainHTMLcontainerBuildingourtasklisttablewithAngulardirectives
www.EBooksWorld.ir
TogglingtasksinourtasklistDisplayingstatechangesinourtemplatesEmbeddingchildcomponents
BuildingourowncustompipesAnatomyofacustompipeAcustompipetobetterformattimeoutputFilteringoutdatawithcustomfilters
BuildingourowncustomdirectivesAnatomyofacustomdirectiveBuildingatasktooltipcustomdirective
AwordaboutnamingconventionsforcustomdirectivesandpipesSummary
5.BuildinganApplicationwithAngular2ComponentsIntroducingthecomponenttreeCommonconventionsforscalableapplications
FileandmodulenamingconventionsEnsuringseamlessscalabilitywithfacadesorbarrels
HowdependencyinjectionworksinAngular2Injectingdependenciesacrossthecomponenttree
RestrictingdependencyinjectiondownthecomponenttreeRestrictingproviderlookup
OverridingprovidersintheinjectorhierarchyExtendinginjectorsupporttocustomentitiesInitializingapplicationswithbootstrap()
SwitchingbetweendevelopmentandproductionmodesEnablingAngular2'sbuilt-inchangedetectionprofiler
IntroducingthePomodoroAppdirectorystructureRefactoringourapplicationtheAngular2way
ThesharedcontextServicesinthesharedcontextConfiguringapplicationsettingsfromacentralservice
CreatingafacademoduleincludingacustomprovidersbarrelCreatingourcomponents
ThetimercontextThetaskscontextDefiningthetoprootcomponent
BootstrappingtheapplicationSummary
6.AsynchronousDataServiceswithAngular2Strategiesforhandlingasynchronousinformation
ObservablesinanutshellReactivefunctionalprogramminginAngular2
TheRxJSlibraryIntroducingtheHTTPAPI
www.EBooksWorld.ir
WhentousetheRequestandRequestOptionsArgsclassesTheResponseobjectHandlingerrorswhenperformingHttprequestsInjectingtheHttpclassandtheHTTP_PROVIDERSmodulessymbol
Arealcasestudy–servingObservabledatathroughHTTPAddingtaskstoourtasksservice
Summary7.RoutinginAngular2
AddingsupportfortheAngular2routerSettinguptherouterservice
BuildinganewcomponentfordemonstrationpurposesConfiguringtheRouteConfigdecoratorwiththeRouteDefinitioninstancesTherouterdirectives–RouterOutletandRouterLinkTriggeringroutesimperativelyCSShooksforactiveroutes
HandlingrouteparametersPassingdynamicparametersinourroutesParsingrouteparameterswiththeRouteParamsservice
DefiningchildroutersLinkingtochildroutes
TheRouterlifecyclehooksTheCanActivatehookTheOnActivateHookTheCanDeactivateandOnDeactivatehooksTheCanReuseandOnReusehooks
AdvancedtipsandtricksRedirectingtootherroutesTweakingthebasepathFinetuningourgeneratedURLswithlocationstrategiesLoadingcomponentsasynchronouslywithAsyncRoutes
Summary8.FormsandAuthenticationHandlinginAngular2
Two-waydatabindinginAngular2TheNgModeldirectiveBindingatypetoaformwithNgModel
BypassingtheCanDeactivaterouterhookuponsubmittingformsTrackingcontrolinteractionandvalidatinginput
TrackingchangeswithlocalreferencesControls,ControlGroups,andtheFormBuilderclass
IntroducingControlsandValidatorsControlsintheDOM–thengControldirectiveGroupingcontrolsintheDOMwithNgControlGroupDefiningcontrolgroupsimperativelywithControlGroupConnectingtheDOMandthecontrollerwithngFormModel
www.EBooksWorld.ir
Arealexample–ourlogincomponentTheloginfeaturecontextTheloginformtemplateThelogincomponentApplyingcustomvalidationtoourcontrolsWatchingstatechangesinourcontrols
MockingaclientauthenticationserviceExposingournewservicetoothercomponentsBlockingunauthorizedaccessMakingtheUIreactivetotheuserauthenticationstatus
RunningtheextramileonaccessmanagementBuildingourownsecureRouterOutletdirective
Summary9.AnimatingComponentswithAngular2
CreatinganimationswithplainvanillaCSSHandlinganimationwithCSSclasshooks
ClasshooksavailableAnimatingcomponentswiththeAnimationBuilder
TheCssAnimationBuilderAPITrackinganimationstatewiththeAnimationclass
DevelopingcustomanimationdirectivesInteractingwithourdirectivefromthetemplate
LookingintothefuturewithngAnimate2.0Summary
10.UnittestinginAngular2Whydoweneedtests?PartsofaunittestinAngular2
DependencyinjectioninunittestsSettingupourtestenvironment
ImplementingourtestrunnerSettingupNPMcommands
Angular2custommatcherfunctionsTestingpipesTestingcomponents
TestingcomponentswithdependenciesOverridingcomponentdependenciesforrefinedtesting
TestingroutesTestingroutesbyURLTestingredirections
TestingservicesTestingasynchronousservicesMockingHttpresponseswithMockBackend
TestingdirectivesTheroadahead
www.EBooksWorld.ir
UsingJasmineincombinationwithKarmaIntroducingcodecoveragereportsinyourteststackImplementingE2Etests
SummaryIndex
www.EBooksWorld.ir
LearningAngular2
www.EBooksWorld.ir
LearningAngular2Copyright©2016PacktPublishing
Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.
Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.
PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.
Firstpublished:May2016
Productionreference:2260516
PublishedbyPacktPublishingLtd.
LiveryPlace
35LiveryStreet
BirminghamB32PB,UK.
ISBN978-1-78588-207-4
www.packtpub.com
www.EBooksWorld.ir
CreditsAuthor
PabloDeeleman
Reviewer
JohannesWeber
CommissioningEditor
SarahCrofton
AcquisitionEditor
ReshmaRaman
ContentDevelopmentEditor
SamanthaGonsalves
TechnicalEditor
MohitaVyas
CopyEditors
RoshniBanerjee
AkshataLobo
ProjectCoordinator
SanchitaMandal
Proofreader
SafisEditing
Indexer
PriyaSane
Graphics
www.EBooksWorld.ir
KirkD'Penha
ProductionCoordinator
NileshR.Mohite
CoverWork
NileshR.Mohite
www.EBooksWorld.ir
AbouttheAuthorPabloDeelemanisaformerUXdesignerandfrontendengineerwhodiscoveredtheWebbackinthe90s,whena14,400bpsmodemwasthekeytoanunparalleledworldofmarvelsandabuild-your-own-websitewasthenameofthegame.
AftergettinghisBA(Hons)degreeinmarketingandmovingthroughdifferentrolesintheadvertisingarena,hetookhischanceandevolvedintoaself-taught,passionateUXdesignerandfrontenddeveloperwithacrunchforbeautifullycraftedCSSlayoutsandJavaScriptthickclients,havingproducedcountlessinteractivedesignsandwebdesktopandmobileapplicationseversince.
Duringtheseyears,hehasfulfilledhiscareerasbothanUXdesignerandfrontenddeveloperbysuccessfullyleadingInternetprojectsforawiderangeofclientsandteams,encompassingEuropeanonlinetraveloperators,SiliconValley-basedstart-ups,internationalheavy-traffictubewebsites,globalbankingportals,orgamblingandmobilegamingcompanies,justtonameafew.Atsomepointalongthisjourney,theriseofNode.jsandsingle-page-applicationframeworksbecameaturningpointinhiscareer,beingcurrentlyfocusedonbuildingJavaScript-drivenwebexperiences.
Afterhavinglivedandworkedinseveralcountries,PabloDeelemancurrentlylivesinBarcelona,Spain,whereheleadsthefrontendendeavorintheBarcelonastudioofGameloft,theworldleaderinmobilegaming,andthehomeofinternationallyacclaimedgames,suchasDespicableMe:MinionsRushandAsphalt8.
Whennotwritingbooksortakingpartinindustryeventsandtalks,hespendsmostofhistimefulfillinghisotherpassion:playingpianoandguitar.
www.EBooksWorld.ir
AcknowledgmentsThebookyouholdinyourhandsrightnowistheresultofalotoftime,effort,andsacrifice.Someonewiselysaidoncethatwritingabookaboutaframeworkinthealphastageislikeaimingatamovingtarget,andindeeditis.Duringthewriting,theauthorandtheteaminvolvedinthisprojectwounduplosingtrackofhowmanytimeswehadrewritteneverythingtoconformtothelatestincarnationoftheframework.Intheheatofthebattle,itisquiteeasytofallundertheweightoffrustrationandseriouslyconsiderwhethersuchaprojectisworththeeffortornot.Inthatsense,thisiswhyIonlyhavewordsofappreciationfortheteamatPacktandmostparticularlyforSamanthaGonsalves.HerkindwordsofsupportfueledtheenergyIneededtomovethisprojectahead.
IwouldalsoliketospeciallythankmyfriendandtechauthorJorgeFerrandoforhisguidanceandhintsduringtheproductionprocessforthisbook.HisexpertiseinAngular2becamepricelesswhenassessingthedifferentcoursesofactiontodeliverthebestlearningexperience.AmentionisrequiredaswellforourotherfellowdevelopersJavierGómez,AlfonsoFernández,FranIruela,andPedroNarciso.
I'dliketoalsothankthepeoplewhohavementoredmeandaccompaniedmealongthisprofessionaljourneyovertheseyears,withaspecialmentionforthepeopleatCasumoandGameloft,andmostspecificallyandinnoparticularorder,forRazmusSvenningson,KimLarsen,JosefGalea,SteveAttard,IdenAzzopardi,RenaldDalli,MatthewBorg,MarkBusuttil,GerardGiné,AntonioGonzález,AlbertPuértolas,RafaelMarfilandthealwaysinspiringStuartLangridge.
www.EBooksWorld.ir
AbouttheReviewerJohannesWeberisapassionatedeveloperandadviserinthefieldofwebtechnologiesspotlightedonenterpriseJSapps.HeworksforMayflowerGmbH(Munich,Germany),wherehefocusesonthemigrationofSPAandMPA.Inhisfreetime,he(co)organizestheAngularJSMunichmeetups,AngularCampandJS-Kongress.de.JohannescofoundedESnextNews.com,whereyougetfivegreatECMAScript.nextlinkseveryweekinyourinbox.
www.EBooksWorld.ir
www.PacktPub.com
www.EBooksWorld.ir
eBooks,discountoffers,andmoreDidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusat<customercare@packtpub.com>formoredetails.
Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.
https://www2.packtpub.com/books/subscription/packtlib
DoyouneedinstantsolutionstoyourITquestions?PacktLibisPackt'sonlinedigitalbooklibrary.Here,youcansearch,access,andreadPackt'sentirelibraryofbooks.
www.EBooksWorld.ir
Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser
ThisbookisdedicatedtomyparentsPaulandPepa,andinthelovingmemoryofmybrotherJoséRaúl.
Youwillliveforeverinourhearts.
www.EBooksWorld.ir
PrefaceOverthepastyears,Angular1.xhasbecameoneofthemostubiquitousJavaScriptframeworksforbuildingcuttingedgewebapplications,eitherbigorsmall.Atsomepoint,itsshortcomingswithregardtoperformanceandscalabilitybecametooprominentassoonasapplicationsgrewinsizeandcomplexity.Angular2wasthenconceivedasafullrewritefromscratchtofulfilltheexpectationsofmoderndevelopers,whodemandblazingfastperformanceandresponsivenessintheirwebapplications.
Angular2hasbeendesignedwithmodernwebstandardsinmindandallowsfullflexibilitywhenpickingupyourlanguageofchoice,providingfullsupportforES6andTypeScript,butworkingequallywellwithtoday'sES5,Dart,orCoffeeScript.Itsbuilt-independencyinjectionfunctionalitieslettheuserbuildhighlyscalableandmodularapplicationswithanexpressiveandself-explanatorycode,turningmaintainabilitytasksintoabreeze,whilesimplifyingtest-drivendevelopmenttothemax.However,whereAngular2standsoutiswhenitshowsoffitsunparalleledlevelofspeedandperformance,thankstoitsnewchangedetectionsystemthatisuptofivetimesfasterthanitspreviousincarnation.Cleanerviewsandanunsurpassedstandards-complianttemplatingsyntaxcompoundanendlesslistofpowerfulfeaturesforbuildingthenextgenerationofwebmobileanddesktopapps.
Angular2isheretostayandwillbecomeagamechangerinthewaymodernwebapplicationsareenvisionedanddevelopedintheyearstocome.However,andduetoitsdisruptivedesignandarchitecture,learningAngular2mightseemadauntingefforttonewcomers.
Thisiswherethisbookcomesin—itsgoalistoavoidbloatingthereaderwithAPIreferencesandframeworkdescriptions,buttoembraceahands-onapproach,helpingthereaderlearnhowtoleveragetheframeworktobuildstuffthatmattersrightfromdayone.Thisislearningbydoingrightfromthestart.
ThisbookaimstogivedevelopersacompletewalkthroughofthisnewplatformanditsTypeScript-flavoredsyntaxbybuildingawebprojectfrombacktoforth,startingfromthebasicconceptsandsamplecomponentsanditeratingonthemtobuildupmorecomplexfunctionalitiesineverychapteruntilwelaunchacomplete,tested,production-readysamplewebapplicationbytheendofthebook.
www.EBooksWorld.ir
WhatthisbookcoversChapter1,CreatingOurVeryFirstComponentinAngular2,introducesthereadertowebcomponents,whicharethebuildingblocksofallAngular2applications.
Chapter2,IntroducingTypeScript,instructsthereaderaboutthesyntaxandparticularitiesofthistypedsupersetofECMAScript6,beinginfactthesyntaxofchoiceoftheAngularteamforbuildingAngular2.
Chapter3,ImplementingPropertiesandEventsinOurComponents,describeshowourcomponentsbehavelikestatemachinesthatcanchangetheirstatebyreceivingdatathroughtheirinputpropertiesandemitdataaseventsthroughtheiroutputproperties.
Chapter4,EnhancingourComponentswithPipesandDirectives,givesacompletewalkthroughoftheframework'sbuilt-inpipesusedtodigestdataoutputinourtemplates,andalsothebuilt-indirectivesthatprovideadvancedfunctionalitytoourcomponent.Thereaderwillalsolearnhowtocreatecustompipesordirectives
Chapter5,BuildinganApplicationwithAngular2Components,devotesanentirechaptertorecapwhatwehavelearnedsofarandorchestrateseverythingtoensureourAngular2projectsscalewellregardlesstheirsizeandconformtothecommunitycodingandnamingconventions.
Chapter6,AsynchronousDataServiceswithAngular2,teachesthereaderhowtoimplementanddeployHTTPconnectionswithotherdataservicesbymeansoftheHttpmodule,sowecancreateourowndataserviceclients.
Chapter7,RoutinginAngular2,introducesthereadertoAngular2'srouteranditsbuilt-indirectives,providingacompletewalkthroughthedifferentstrategieswehavetoloadcomponentsfromroutesandhandlethestatethroughtheHistoryAPI.
Chapter8,FormsandAuthenticationHandlinginAngular2,illustratesthedifferentstrategieswehaveatourdisposaltobuildwebformswithAngular2,managetwo-waydatabindingoninputcontrols,andcreatecomplexformsandvalidations.
Chapter9,AnimatingComponentswithAngular2,coversthecurrentlyavailabletoolsandclassesforimplementinganimationsonourcomponents,frompureCSSanimationshandledwithAngular2directivestomorecomplextransitionspurelymanagedthroughJavaScript,thankstoAngular2animationbuilders.
Chapter10,UnitTestinginAngular2,willguidethereaderthroughthestepsrequiredforimplementingasoundtestingfoundationinourapplication,andthegeneralpatternsfordeployingunittestsoncomponents,directives,pipes,routes,andservices.
www.EBooksWorld.ir
WhatyouneedforthisbookInordertodeveloptheexamplescontainedinthisbook,youwillprimarilyneedawebbrowserupdatedtoitslatestversion.WerecommendGoogleChromeorMozillaFirefox,althoughAngular2ismeanttobesupportedinallevergreenbrowsers.
YouwillalsoneedterminalsoftwareinstalledonyourOS,sincemanyoperationsarehandledthroughnpmcommandstotheconsole.Inthissense,havingNode.jsandnpminstalledonyoursystemwillberequiredtorunmostoftheconsolecommandsmentionedinthebook.Therestofmodulesrequiredandtheirinstallationprocedurewillbedescribedaswego.
Last,butnotleast,youwillrequireatexteditortocodeyourAngular2modules,althoughChapter1,CreatingOurVeryFirstComponentinAngular2willprovideathoroughwalkthroughofallthebestIDEalternativesinstorenowadaysfordevelopingAngular2applications.
www.EBooksWorld.ir
WhothisbookisforThisbookistargetedatwebdeveloperswhowanttobuildthenextgenerationofstate-of-the-artmobileanddesktopwebapplicationswithAngular2.ThisbookdoesnotrequireyoutohavepriorexposuretoeitherAngular1.xor2,althoughcomprehensiveknowledgeofJavaScriptisassumed.It'sgreatfornewcomerstoAngularwholearnbestthroughclearexplanationsanddefinitionofconcepts.
www.EBooksWorld.ir
ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.
Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"Asaresultofthisaction,wewillfindanewtsconfig.jsonfileattherootofourproject,includingthesettingsrequiredbytheTypeScriptcompilertotranspilethecomponentcodeintoplainECMAScript5JavaScriptcodereadablebycurrentbrowsers."
Ablockofcodeissetasfollows:
<body>
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroTimer</strong>
</div>
</div>
</nav>
<pomodoro-timer></pomodoro-timer>
</body>
Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:
<body>
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroTimer</strong>
</div>
</div>
</nav>
<pomodoro-timer></pomodoro-timer>
</body>
Anycommand-lineinputoroutputiswrittenasfollows:
$npminstallangular2es6-shimes6-promisereflect-metadatarxjszone.js--
save
Newtermsandimportantwordsareshowninbold.Wordsthatyouseeonthescreen,forexample,inmenusordialogboxes,appearinthetextlikethis:"Thelearnsectiongivesusaccesstoaquicktutorialtogetuptospeedwiththelanguageinnotime."
Note
Warningsorimportantnotesappearinaboxlikethis.
www.EBooksWorld.ir
Tip
Tipsandtricksappearlikethis.
www.EBooksWorld.ir
ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook—whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.
Tosendusgeneralfeedback,simplye-mail<feedback@packtpub.com>,andmentionthebook'stitleinthesubjectofyourmessage.
Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.
www.EBooksWorld.ir
CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.
www.EBooksWorld.ir
DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromGitHubathttps://github.com/deeleman/learning-angular2.
Youcandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORT tabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou'relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.
YoucanalsodownloadthecodefilesbyclickingontheCodeFilesbuttononthebook'swebpageatthePacktPublishingwebsite.Thispagecanbeaccessedbyenteringthebook'snameintheSearchbox.PleasenotethatyouneedtobeloggedintoyourPacktaccount.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
www.EBooksWorld.ir
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.
www.EBooksWorld.ir
PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.
Pleasecontactusat<copyright@packtpub.com>withalinktothesuspectedpiratedmaterial.
Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.
www.EBooksWorld.ir
QuestionsIfyouhaveaproblemwithanyaspectofthisbook,youcancontactusat<questions@packtpub.com>,andwewilldoourbesttoaddresstheproblem.
www.EBooksWorld.ir
Chapter1.CreatingOurVeryFirstComponentinAngular2Unlessyouwerelostinspaceforthepastcoupleofyears,chancesareyouarewellawareofthemomentumthatmodernJavaScriptwebframeworksandlibrarieshavegotinthefrontendarenanowadays.Wehaveevenreachedastagewhereanewframeworkisborneveryday,forcingfrontenddeveloperstoassesscarefullywhetherthisnewcutting-edgecodetoolkitaddsenoughvaluetojustifythetimeandeffortrequiredtofaceitslearningcurveandputittogooduseinournextproject.
Eventually,ahandfulofnamesendedupgainingmorerelevancethantherest.Weareobviouslyreferringtoclient-sideframeworksthatwillprobablysoundprettyfamiliartoyoualready:Backbone,Ember,Knockout,Angular1,andsoon.
AsthebattleforsupremacyintheJavaScriptworldcarriedon,newframeworkssuchasReactorAureliaenteredthegame,favoringwebcomponentsandharnessingthepowerofShadowDOMasthecornerstoneofitsarchitecture.Applicationsbuiltthiswayprovedtobemoremodular,scalable,andmaintainable,letalonetheirunparalleledlevelofperformance.
Angular1hadcomealongwayalreadysinceitsinceptionanditsshortcomingshadbecometooprominenttobeoverlookedanylonger.Itwastimeforsomethingbetterandasimplerevampofcodebasedidnotsuffice.AmoreambitiousapproachwasrequiredandAngular2wasdeveloped—anewframeworkengineeredfromscratch,whichfullyembracesthenewesttrendsintheindustry.IthaswebcomponentsattheheartofitsdesignanditharnessesthepowerofShadowDOMtomaximizetheresponsivenessofourwebentitiesagainststatechanges.Ontopofthat,Angular2offersastate-of-the-artchangedetectionsystembakedintoeachcomponent,whichisresponsibleforpropagatingbindingsthroughoutthetreeofcomponentsthatcompriseourapplications.
ThedefiningtraitsofAngular2gobeyondtheconceptofjustbeingamerewebcomponentsframework,sinceitsfeaturesencompassprettymucheverythingyouneedinamodernwebapplication:componentinteroperability,universalsupportformultipleplatformsanddevices,atop-notchdependencyinjectionmachinery,aflexiblebutadvancedroutermechanismwithsupportfordecouplingandcomponentizationofroutedefinitions,advancedHTTPmessaging,andanimationorinternationalization,justtonameafew.
Inthischapter,wewill:
LearnwhyAngular2issouniqueincomparisontoitspreviousversionsLearnhowtosetupourcodeenvironmenttoworkwithAngular2andTypeScriptEnhanceourIDEofchoicetoprovideabetterexperiencecodingAngular2appsBuildourveryfirstAngular2webcomponentandlearnhowtoembeditonawebpageAddbasicinteractivityfeaturestoourwebcomponentDiscoversomebasichelperstobetterformatthedataoutput
www.EBooksWorld.ir
AfreshstartAsmentionedbefore,Angular2representsafullrewriteoftheAngular1.xframework,introducingabrandnewapplicationarchitecturecompletelybuiltfromscratchinTypeScript,astrictsupersetofJavaScriptthataddsoptionalstatictypingandsupportforinterfacesanddecorators.
Inanutshell,Angular2applicationsarebasedonanarchitecturedesignthatcomprisestreesofwebcomponentsinterconnectedbetweenthembytheirownparticularI/Ointerface.Eachcomponenttakesadvantageunderthecoversofacompletelyrevampeddependencyinjectionmechanism.Tobefair,thisisasimplisticdescriptionofwhatAngular2reallyis.However,thesimplestprojectevermadeinAngulariscutoutbythesedefinitiontraits.Wewillfocusonlearninghowtobuildinteroperablecomponentsandmanagedependencyinjectioninthenextchapters,beforemovingontorouting,webforms,orHTTPcommunication.ThisalsoexplainswhywewillnotmakeexplicitreferencestoAngular1.xthroughoutthebook.Obviously,itmakesnosensetowastetimeandpagesreferringtosomethingthatwillnotprovideanyusefulinsightsonthetopic,besidesthefactweassumethatyoumightnotknowaboutAngular1.x,sosuchknowledgedoesnothaveanyvaluehere.
www.EBooksWorld.ir
WebcomponentsWebcomponentsisaconceptthatencompassesfourtechnologiesdesignedtobeusedtogethertobuildfeatureelementswithahigherlevelofvisualexpressivityandreusability,therebyleadingtoamoremodular,consistent,andmaintainableweb.Thesefourtechnologiesareasfollows:
Templates:ThesearepiecesofHTMLthatstructurethecontentweaimtorenderCustomElements:ThesetemplatesnotonlycontaintraditionalHTMLelements,butalsothecustomwrapperitemsthatprovidefurtherpresentationelementsorAPIfunctionalitiesShadowDOM:ThisprovidesasandboxtoencapsulatetheCSSlayoutrulesandJavaScriptbehaviorsofeachcustomelementHTMLImports:HTMLisnolongerconstrainedtohostHTMLelements,buttootherHTMLdocumentsaswell
Intheory,anAngular2componentisindeedacustomelementthatcontainsatemplatetohosttheHTMLstructureofitslayout,thelatterbeinggovernedbyascopedCSSstylesheetencapsulatedwithinaShadowDOMcontainer.Let'strytorephrasethisinplainEnglish.ThinkoftherangeinputcontroltypeinHTML5.Itisahandywaytogiveourusersaconvenientinputcontrolforenteringavaluerangingbetweentwopredefinedboundaries.Ifyouhavenotuseditbefore,insertthefollowingpieceofmarkupinablankHTMLtemplateandloaditinyourbrowser:
<inputid="mySlider"type="range"min="0"max="100"step="10">
Youwillseeaniceinputcontrolfeaturingahorizontalsliderinyourbrowser.InspectingsuchcontrolwiththebrowserdevelopertoolswillunveilaconcealedsetofHTMLtagsthatwerenotpresentatthetimeyoueditedyourHTMLtemplate.ThereyouhaveanexampleofShadowDOMinaction,withanactualHTMLtemplategovernedbyitsownencapsulatedCSSwithadvanceddraggingfunctionality.Youwillprobablyagreethatitwouldbecooltodothatyourself.Well,goodnewsisthatAngular2givesyouthetoolsetrequiredfordeliveringthisverysamefunctionality,sowecanbuildourowncustomelements(inputcontrols,personalizedtags,andself-containedwidgets)featuringtheinnerHTMLmarkupofourchoiceandaveryownstylesheetthatdoesnotaffect(norisimpacted)bytheCSSofthepagehostingourcomponent.
www.EBooksWorld.ir
WhyTypeScriptoverothersyntaxes?Angular2applicationscanbecodedinawidevarietyoflanguagesandsyntaxes:ECMAScript5,Dart,ECMAScript6,TypeScript,orECMAScript7.
TypeScriptisatypedsupersetofECMAScript6(alsoknownasECMAScript2015)thatcompilestoplainJavaScriptandiswidelysupportedbymodernOSes.Itfeaturesasoundobject-orienteddesignandsupportsannotations,decorators,andtypechecking.
Thereasonwhywepicked(andobviouslyrecommend)TypeScriptasthesyntaxofchoiceforinstructinghowtodevelopAngular2applicationsinthisbookisbasedonthefactthatAngular2itselfiswritteninthislanguage.BeingproficientinTypeScriptwillgivethedeveloperanenormousadvantagewhenitcomestounderstandingthegutsoftheframework.
Ontheotherhand,itisworthremarkingthatTypeScript'ssupportforannotationsandtypeintrospectionturnsouttobeparamountwhenitcomestomanagingdependencyinjectionandtypebindingbetweencomponentswithaminimumcodefootprint,aswewillseefurtherdownthelineinthisbook.
Ultimately,youcancarryoutyourAngular2projectsinplainECMAScript6syntaxifthatisyourpreference.EventheexamplesprovidedinthebookcanbeeasilyportedtoES6byremovingtypeannotationsandinterfaces,orreplacingthewaydependencyinjectionishandledinTypeScriptwiththemostverboseES6way.
Note
Forthesakeofbrevity,wewillonlycoverexampleswritteninTypeScriptandactuallyrecommenditsusebecauseofitshigherexpressivitythankstotypeannotations,anditsneatwayofapproachingdependencyinjectionbasedontypeintrospectionoutofsuchtypeannotations.
www.EBooksWorld.ir
SettingupourworkspaceBeforejumpingintotheimplementationofourveryfirstandshinyAngular2component,weneedtobringinallthetoolswewillrequiretoimplementsoftwarebasedonTypeScript,letalonetheAngular2frameworkmodulesthemselves.
Firstandforemost,createafolderanddoublecheckthattheNPMCLIisavailableinyoursystemandisproperlyupdatedtothelateststableversion.Otherwise,pleasegotohttps://nodejs.organdinstallthelatestNode.jsruntime.
Note
Atthetimeofwriting,theAngular2frameworkisinReleaseCandidate1version,sotherequirementsforbuildinganddeployingtheexamplescontainedinthisbookmighthavechangedovernight.Theauthormaintainsacoderepositoryathttps://github.com/deeleman/learning-angular2,whereyoucancheckthemostup-to-dateversionofeachexamplecontainedinthisbook.Therepositoryisdividedintochapterfoldersandeachfoldercontainstheincrementalversionoftheprojectasitisattheendofeachchapter.Pleaserefertothecoderepositoryshouldanyproblemariseuponinstallingordeployingtheexamplesinthebook.
www.EBooksWorld.ir
InstallingdependenciesOurfirstrequirementwillobviouslybetoinstallAngular2ontoourworkspace,includingitsownpeerdependencies.TheAngular2teamhasmadeagreatefforttoensuretheinstallationismodularenoughtoallowustobringonlywhatweneed,becomingourprojectsmoreorlessleandependingontherequirements.
Inthissense,Angular2doesnotcomeintheformofasingleinstallablepackage,butmany.Thisgivesthesmartdevelopertheopportunitytopickonlythosemodulesthatarerequiredforitsproject,minifyingtheoveralldependenciesfootprint.Someofthesepackages,suchascommonorcore,arerequiredregardlessthetypeofprojectwewanttoship.Someothers,suchasplatform-browser-dynamic,areboundtothetypeofprojectandtargetplatformaddressed.Anonthoroughlistofthemostcommonpackagesthatyouwillrequireinyourprojectsisgivenhere:
@angular/core:Thisisthemostrelevantpackage,encompassingthebackboneofAngularanditsmostcommonelements,suchasdirectivesandcomponents.YouwillneedtorelyonthismoduleonacommonbasistoimportthebasicelementsofAngular2intoyourproject.@angular/common:Youwillseldomneedtoexplicitlyimporttokensfromthismodule,butitisworthremarkingthatthispackagecontainsthedefinitionsofallthedirectives,services,andpipescontainedbyAngular2,amongotherrelevantclasses.@angular/compiler:Sameascommon,youwillrarelyimporttokensexplicitlyfromthismodule,althoughitistheoneresponsibleforcompilingtheHTMLtemplatesandturningthemintocodethatcanrendertheapplication'sUIoutput.@angular/platform-browser:ThismodulecontainsclassesandfunctionsrequiredforcomposingandinteractingwiththeDOMinawebbrowsercontext.Updatingthepagetitleorconfiguringthetouchgesturessetuparecommonactionsmadepossiblebythismodule.Thispackagealsocontainsthefunctionsrequiredtocompiletemplatesofflineinproductionenvironments.@angular/platform-browser-dynamic:Wewillrelythoroughlyonthismoduleduringthecourseofthebook,sinceitwillprovideuswiththebootstrappingfunctionwewillrequiretoinitializeourapplicationsondevelopment.@angular/http:ItistheAngular2HTTPclient,whichwewillcoverindetailinChapter6,AsynchronousDataServiceswithAngular2.@angular/router:ItistheAngular2built-inrouterstillunderBetaatthetimeofthiswriting.@angular/router-deprecated:AsnapshotofthepreviousincarnationoftheAngular2built-inrouter,madeavailabletoensurebackwardcompatibilitywithexistingapplications.Chapter7,RoutinginAngular2,willcoveritindetailandexplainsomeofitsmostremarkabledifferenceswiththenewrouterstillunderdevelopment.
Atthetimeofwriting,theseareallthedifferentthird-partylibrariesthatarerequiredaspeerdependenciesinanAngular2project,apartfromtheAngular2modules:
www.EBooksWorld.ir
es6-shim:ThisintroducesECMAScript6compatibilitypolyfillsforlegacyJavaScriptengines(mostlyMicrosoftInternetExplorer).ThisdependencyisnowrequiredbecausemanymajorbrowsersstilldonotprovidewidesupportforECMAScript6features,buthopefully,thiswillchangesoon.Someotherimplementationsusethecore-jsstandardlibraryinstead.Ultimately,picktheoneyoulikethemostaslongasitproperlypolyfillsthecoreES2015APIsrequiredbyAngular2.zone.js:ThisisapolyfillfortheZonespecificationthatisusedtohandlechangedetectioninAngular2applications.reflect-metadata:ThisbringssupportfordecoratorsinourAngular2classesandmetadatareflectioninourcomponents.WewillseedecoratorsinactionlateroninthischapterandabroaderoverviewofitsdifferenttypesandimplementationsinChapter2,IntroducingTypeScript.DecoratorsareacorepartofAngular2.rxjs:ThislibrarywasdevelopedbyMicrosoftOpenTechnologies,Inc.AccordingtoMicrosoft,itisasetoflibrariestocomposeasynchronousandevent-basedprogramsusingobservablecollectionsandArray#extrasstylecompositioninJavaScript.Inshort,RxJSisalibraryformanagingObservables,whichallowustomakeourapplicationsfullyreactivetoasynchronousstatechanges.TheObservablesspecwillbestandardizedbymodernbrowsersinthefuture,sowewillbeabletoruleoutthisdependencybythen.
ThesedependenciesmayevolvewithoutpriornoticesopleaserefertotheGitHubrepositoryforthemostup-to-datelistofrequirements.
Note
YouwillbeprobablysurprisedbytheamountoflibrariesthatAngular2doesneedandthefactthatthesedependenciesarenotpartoftheAngularbundleitself.ThisisbecausetheserequisitesarenotspecifictoAngular2,butofavastmajorityofmodernJavaScriptapplicationsnowadays.
Withallthesedependenciesandthird-partylibrariesinmind,youcanrunthefollowingsetofbashcommandsinyourterminalconsole,onceyouhavecreatedafolderfortheprojectwewillcoverinthisbook:
$mkdirlearning-angular2
$cdlearning-angular2
$npminit
$npminstall@angular/common@angular/core@angular/compiler--save
$npminstall@angular/platform-browser@angular/platform-browser-dynamic--
save
$npminstall@angular/router@angular/router-deprecated--save
$npminstall@angular/http--save
$npminstalles6-shimreflect-metadatarxjszone.js--save
Apartfromthedependenciesenlistedpreviously,wewillalsoneedtoinstallthesystemjsuniversalmoduleloaderpackageinordertosupportmoduleloadingbetweencodeunitsoncetranspiledintoES5.ThesystemjspackageisnottheonlyoptionavailableformanagingmoduleloadinginAngular2.Infact,wecanswapitforothermoduleloaders,suchas
www.EBooksWorld.ir
WebPack(https://webpack.github.io/),althoughalltheexamplesprovidedinthisbookmakeuseofSystemJSforhandlingcodeinjection.WewillinstallSystemJS,flaggingitasadevelopmentdependencybyexecutingthefollowingcommand:
$npminstallsystemjs–save
Last,butnotleast,wewillalsoinstallBootstrapinourapplicationsothatwecaneasilycraftaniceUIfortheexampleapplicationwewillbuildincrementallyineachchapter.ThisisnotanAngular2requirement,butaparticulardependencyoftheprojectwewillcarryoutthroughoutthisbook:
$npminstallbootstrap–save
TheinstallationcanthrowdifferentalertsandwarningsdependingontheversionsofeachpeerdependencyrequiredbyAngular2atthismomentintime,soincaseofissues,Istronglyrecommendtofetchthelatestversionofthepackage.jsonfileavailableinthisbook'scoderepositoryhttps://github.com/deeleman/learning-angular2/blob/master/chapter_01/package.json.
Downloadthefiletoyourdirectoryworkspaceandrunthenpminstallcommand.NPMwillfindandinstallallthedependenciesforyouautomatically.
Note
MacOSusers,whohavenotclaimedownershiprightsonthenpmdirectorylocatedat/usr/local/bin/npm(or/usr/local/npmforthoseusersonOSversionspriortoMacOSElCapitan),mightneedtoexecutethenpminstallcommandwithsudoprivileges.
www.EBooksWorld.ir
InstallingTypeScriptWehavenowacompletesetofAngular2sourcesandtheirdependencies,plustheBootstrapmoduletobeautifyourprojectandSystemJStohandlemoduleloadingandbundlegeneration.
However,TypeScriptisprobablynotavailableonyoursystemyet.Let'sinstallTypeScriptandmakeitgloballyavailableonyourenvironmentsothatwecanleverageitsconvenientCLItocompileourfileslateron:
$npminstall-gtypescript
Great!We'realmostdone.OnelaststepentailsinformingTypeScriptabouthowwewanttousethecompilerwithinourproject.Todoso,justexecutethefollowingone-timecommand:
$tsc--init--experimentalDecorators--emitDecoratorMetadata--targetES5-
-modulesystem--moduleResolutionnode
Basically,wehavejustinitializedaTypeScriptproject(whichisourAngular2projectitself)withsupportforexperimentaldecorators(aswementionedalready,theseareanewfeatureinES7andTypeScriptthatAngular2usesextensively)andsetSystemJSasthedefaultmechanismforimportingmodulesanddependenciesbetweenfiles.
Asaresultofthisaction,wewillfindanewtsconfig.jsonfileattherootofourproject,includingthesettingsrequiredbytheTypeScriptcompilertotranspilethecomponentcodeintoplainECMAScript5JavaScriptcodereadablebycurrentbrowsers.
Note
PleaserememberthatourbrowsersdonotprovidesupportforTypeScriptorECMAScript6outofthebox,sowewilltranspileourcodetosomeflavorofJavaScriptthatiswidelysupportedbyourtargetbrowsers.
Asneakpeekonsuchfilewillyieldthefollowing:
{
"compilerOptions":{
"experimentalDecorators":true,
"emitDecoratorMetadata":true,
"target":"es5",
"module":"system",
"moduleResolution":"node",
"noImplicitAny":false,
"outDir":"built",
"rootDir":".",
"sourceMap":false
},
"exclude":[
"node_modules"
]
www.EBooksWorld.ir
}
Simple,right?Thesetofpropertiesincludedinourconfigmanifestisself-descriptiveenough,butwecanhighlightthreeinterestingproperties.Theyareasfollows:
rootDir:ThispointstothefolderthecompilerwillusetoscanforTypeScriptfilestocompile(currentlythebasefolderinourexample).outDir:Thisdefineswherethecompiledfileswillbemovedunlesswedefineourownoutputpathbymeansofthe--outDirparameterinthecommandline,thecompilerwilldefaulttothebuiltfoldercreatedatruntimeinthesamelocationwherethetsconfig.jsonfilelives.sourceMap:Thissetsthesourcecodemappingpreferencestohelpdebugging.
Toggleitsvaluetotrueifyouwantsourcemapfilestobegeneratedatruntimetobacktracethecodetoitssourcethroughthebrowser'sdevtoolsincaseexceptionsarise.
Besidestheseproperties,wealsocanseethatwehavemarkedthenode_modulesfolderasexcluded,whichmeansthatthetsccommandwillskipthatfolderandallitscontentswhentranspilingTypeScriptfilestoES5throughouttheapplicationtree.
Tip
IwouldencourageyoutorefertotheTypeScriptcompilerwikiathttps://github.com/Microsoft/TypeScript/wiki/Compiler-OptionsforafullrundownofoptionsavailableinthecompilerAPI.
www.EBooksWorld.ir
InstallingTypeScripttypingsBesidestheprojectdependencies,suchasBootstrapandAngular2'sowndependencies,TypeScriptdoesrequiresomeadditionallibrariessowecangetthebestoutofit.Specifically,ES6extendstheJavaScriptenvironmentwithmethodsandAPIsthatneedtobedescribedtotheTypeScriptcompiler.Otherwise,itwillnotrecognizethemaspartofthesyntaxandwillthrowerrorsuponcompiling.WheneverweneedtoinstructtheTypeScriptcompileraboutaJavaScriptAPI,eitheranativeoneoranyotherAPIbelongingtoathirdpartylibrary,wewillwanttouseaTypeScripttypedefinitionfile.
ATypeScripttypedefinitionfileisbasicallyafilewiththed.tsfileextensionthatcontainsTypeScriptinterfaces(moreonthisinChapter2,IntroducingTypeScript)sowecanbetterperformreal-timetypecheckingandpreventcompilererrors.Installingtypedefinitionfilesinourprojectsisnotabigdealandjustrequireshavingatypingstoolinstalledinourenvironment.Infact,weneedtoinstallatypedefinitionfiletoensurethattheTypeScriptcompilerisacquaintedwiththemostup-to-dateES6API.GoodnewsisthatwecaninstallaTypeScriptdefinitionsmanagertoolrightfromtheNPMregistry,sowecanautomatetheprocessofsearching,installinganddeployingtypedefinitionfiles.Therefore,returntotheconsoleandproceedwiththefollowingcommands:
$npminstall-gtypings
$typingsinstalles6-shim--ambient--save
First,weinstallthetypingstoolgloballyandthenweleveragethetypingsCLItoinstallthees6-shimtypesdefinitionfileintoourproject,creatingthetypings.jsonfilethatwillstorethereferencestothesourceoriginforalltypedefinitionfileswewillinstallnowandlateron.Anewfoldernamedtypingsiscreatedanditcontainsthedefinitionfileswerequire.Withoutthem,basicES6featureslikethenewfunctionalmethodsoftheArrayclasswouldnotbeavailable.
Beforemovingforward,weneedtotackleonemorestepregardingtheTypeScripttypings.Wheninstallingtypedefinitionfiles,twofaçadefilesaregeneratedbytheCLI:typings/main.d.tsandtypings/browser.d.ts.However,onlyoneshouldbeexposedtotheTypeScriptcompiler.Otherwise,itwillraiseanexceptionafterfindingduplicatedtypedefinitions.Sincewearebuildingfrontendapplications,wewillsticktobrowser.d.tsandexcludemain.d.tsanditslinkeddefinitionfilesbymarkingitasexcludedattsconfig.json:
{
"compilerOptions":{
"experimentalDecorators":true,
"emitDecoratorMetadata":true,
"target":"es5",
"module":"system",
"moduleResolution":"node",
"noImplicitAny":false,
"outDir":"built",
"rootDir":".",
"sourceMap":false
www.EBooksWorld.ir
},
"exclude":[
"node_modules",
"typings/main.d.ts",
"typings/main"
]
}
Ontheotherhand,itisactuallyrecommendedtoexcludethetypingsfolderfromyourprojectdistributionbyincludingitinyour.gitignorefile,sameasweusuallydowiththenode_modulesfolder.Youonlywanttoincludethetypings.jsonmanifestwhendistributingyourappandthenhavealltheinstallationprocesseshandledbynpm,soitisveryconvenienttoincludethetypedefinitionfilesinstallationasanactionhandledbythepostinstallscriptinthepackage.jsonfile.Thisway,wecaninstallthenpmdependenciesandthedefinitionfilesinoneshot.Thecodeisasfollows:
"scripts":{
"typings":"typings",
"postinstall":"typingsinstall"
},
Whentakingthisapproach,thetypingspackageshouldbeincludedinthepackage.jsonaspartofthedevelopmentdependencies.Thus,reinstallitwiththe--save-devflag.Again,pleaserefertothebookcoderepositoryatGitHubtofetchthelatestversionofthepackage.jsonfileforthischapter.
www.EBooksWorld.ir
Hello,Angular2!WiththeAngular2librarybundleinplaceandfullsupportforTypeScriptnowavailable,thetimehascometoputeverythingtothetest.First,createandemptyfilenamedhello-angular.ts(.tsisthenaturalextensionforTypeScriptfiles)attherootofourworkingfolder.
Note
Here,westumbleuponthefirstofmanycodingconventionswewillcoverinthisbook:filenaming.Wenameourmodulefilesusinglowerkebabcase.InChapter5,BuildinganApplicationwithAngular2Components,wewilldelvedeeperintonamingconventionsandbestpracticesforcodingAngular2applications.Untilthen,wewillconcurintosomeanti-patternsforlearningpurposes,asthosemoreexperiencedreaderswillsoonnotice.
Now,openthatfileandwritethefollowingatthetop:
import{Component}from'@angular/core';
import{bootstrap}from'@angular/platform-browser-dynamic';
Wehavejustimportedthemostbasictypeandfunctionwewillneedtoscaffoldaverybasiccomponentinthenextsection.TheimportingsyntaxwillbefamiliartothosewhoarealreadyfamiliarwithECMAScript6.Forthosewhoarenotfamiliarwithitscodeparadigm,don'tworry.WewilldiscussmoreonthisinChapter2,IntroducingTypeScript.
www.EBooksWorld.ir
TypeScriptclassesLet'snowdefineaclass:
classHelloAngularComponent{
greeting:string;
constructor(){
this.greeting='HelloAngular2!';
}
}
ECMAScript6(andTypeScriptaswell)introducedclassesasoneofthecorepartsofitsbuildingblocks.Ourexamplefeaturesaclassfieldpropertynamedgreetingtypedasstring,whichispopulatedwithintheconstructorwithatextstring,asyoucanseeintheprecedingcode.Theconstructorfunctioniscalledautomaticallywhenaninstanceoftheclassiscreated,andeachandeveryproperty(andfunctionsaswell)shouldbeannotatedwiththetypeitrepresents(orreturnsinthecaseoffunctions).
Donotworryaboutallthisnow.Chapter2,IntroducingTypeScript,willgiveyoutheinsightsyouneedtobetterunderstandthemechanicsofTypeScript.Now,let'sfocusontheactuallayoutofourcomponent.Youhaveprobablynoticedthenamestructure,whichconformstoanothercommoncodingconventioninAngular2.WedefineeachandeveryclassinPascalcasingbyappendingasuffixpointingoutitstype(willitbeacomponent,directive,pipe,andsoon),whichisComponentforthiscase.
www.EBooksWorld.ir
IntroducingmetadatadecoratorsThecontrollerclasswehavejustcreatedgivesusthemachineryweneedtoinstanceanobjectexposingagreetingproperty,butwestillneedtoapplysomeAngular2sugartoturnitintoanactualcomponent.WealreadyimportedtheComponentmetadataclass,remember?Let'sputittoworkanddecorateourclasslikethis:
@Component({
selector:'hello-angular',
template:'<h1>{{greeting}}</h1>'
})
classHelloAngularComponent{
greeting:string;
constructor(){
this.greeting='HelloAngular2!';
}
}
AdecoratorisaveryinterestingexperimentalfeatureproposedbyECMAScript7thatwaslaterembracedandimplementedbyTypeScriptinordertodecorateclasseswithmetadata.Thereareseveraltypesofdecoratorsandallofthemareeasilyrecognizablebythe@symbolprefix.AlthoughChapter2,IntroducingTypeScript,willgiveyouanideaaboutdecorators,delvingdeeperintoitscorelogicisoutofthescopeofthisbook.However,wewillgetusedtothemasweadvancethroughthebook.
Inthisexample,wearetellingthecompilerthattheHelloAngularComponentclassis,infact,anAngular2component.Thecomponentismeanttobeencapsulatedbythe<hello-angular>customelementandthetemplatepropertydefinestheinternalHTMLstructureofourcomponent.Aswealreadymentionedpreviously,customelementsencapsulatingHTMLtemplatesarethefoundationofwebcomponents.
www.EBooksWorld.ir
CompilingTypeScriptintobrowser-friendlyJavaScriptWearedonewithourprimeronTypeScript,butunfortunatelyitisquitelikelythatourbrowserofchoicewillnotsupportTypeScript.So,weneedtocompileoursourcecodeintogoodoldECMAScript5JavaScriptcode.
ThegoodnewsisthattheTypeScriptCLIcontainstoolstocompileTypeScriptintoJavaScriptcodeoutofthebox.Todothis,justopenaterminalwindowandtypethefollowingcommandatthelocationofthehello-angular.tsfile:
$tsc--watch
Anewhello-angular.jsfilewillshowupwithinthebuiltdirectory(orthepathyouhavedefinedintheoutDirpropertyattsconfig.json),anditwillcontainanECMAScript5versionoftheTypeScriptcodewejustbuilt.ThisfilealreadycontainssomefunctionalcodetoimplementsupportfortheMetadatadecoratorweconfigured.
Tip
Pleasenotethe--watchflaginourcommand.Thisparameterinformsthecompilerthatwewantthecompilationtobeautomaticallytriggeredagainuponchanginganyfile.Disregardtheflagwhenyoujustneedtocompileyourstuffonceanddonotneedtowatchthecodeforchanges.
Ourcomponentislookingbetterbytheminuteandnowweareinagoodstatetostartusingit,butwestillneedtoembeditsomewhereinordertoseeitlive.It'stimetodefinetheHTMLshellorwebcontainerwhereitwilllive.
www.EBooksWorld.ir
TheHTMLcontainerCreateanHTMLfileattherootofourworkspaceandnameitindex.html.Then,populateitwiththefollowingcode:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>HelloAngular2!</title>
<scriptsrc="node_modules/es6-shim/es6-shim.min.js"></script>
<scriptsrc="node_modules/zone.js/dist/zone.js"></script>
<scriptsrc="node_modules/reflect-metadata/Reflect.js"></script>
<scriptsrc="node_modules/systemjs/dist/system.js"></script>
<scriptsrc="node_modules/rxjs/bundles/Rx.js"></script>
<scriptsrc="systemjs.config.js"></script>
</head>
<body>
</body>
</html>
Thisisthemostbasic,barebonesversionofanHTMLcontainerforanAngular2applicationwecancomeupwith.Thisisgreatbecausemostofthepresentationlogicanddependencymanagementwillbehandledbyourcomponentitself.
However,twothingscatchourattentioninthistemplate.Ononehand,wefindablockofscriptincludes.ThisblockcontainsallthepeerdependencieswerequiretopolyfillES2015(ES6)functionalities;theZoneandObservablesspecificationsensurefullsupportformetadatadecoratorsandlast,butnotleast,implementdynamicmoduleloadingfunctionalitiestoourapplication.
Note
Donottrytoreshufflethesortinglayoutofthesecodeblocksunlessyouwanttofaceunexpectedexceptions.
Then,weintroduceascriptincludepointingtoanewfilenamedsystemjs.config.js.Thisfileisyettobewritten,solet'screateitintherootofourprojectfolderwiththefollowingimplementation.Wewillbreakdownthissetupinthenextparagraphs:
(function(){
varpathMappings={
'@angular':'node_modules/@angular',
'rxjs':'node_modules/rxjs',
www.EBooksWorld.ir
};
varpackages=[
'@angular/common',
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/router-deprecated',
'@angular/testing',
'rxjs',
'built',
];
varpackagesConfig={};
packages.forEach(function(packageName){
packagesConfig[packageName]={
main:'index.js',
defaultExtension:'js'
};
});
System.config({
map:pathMappings,
packages:packagesConfig,
});
})();
Aswecansee,thesystemjs.config.jsfilecontainsprimarilyanimmediatelyinvokedfunctionexpression.Thismeansthatthisblockofcodewillbeexecutedassoonasitisparsedbythebrowser.Let'sovervieweachandeverypieceofthisroutine:
varpathMappings={
'@angular':'node_modules/@angular',
'rxjs':'node_modules/rxjs',
};
Here,wedefinedanobjectliteralcontainingkey/valuepairsofindexescontainingfullpaths.WewillusethisobjectliteraltoconfigurethepathaliasesinSystemJSlateroninthissamefile.Therefore,executingtheimport'@angular/core'statementwillinstructSystemJStoactuallyimportthecorepackagefromnode_modules/@angular/core.However,packagesarenotimportedasawholeasis.Weneedanentrypointtosuchpackageand,therefore,weneedtoinformSystemJSwhatfaçadefileitshouldseekwhenimportinganentiremodule.Thiswewilldobydefininganarrayofmodulesandthencreatinganobjectliteral,whereeachmodulepathisthekeyofapropertycontainingthemainentrypointandthedefaultfileextensionconfigurationobjectforsuchpaths:
varpackages=[
'@angular/common',
www.EBooksWorld.ir
'@angular/compiler',
'@angular/core',
'@angular/http',
'@angular/platform-browser',
'@angular/platform-browser-dynamic',
'@angular/router',
'@angular/router-deprecated',
'@angular/testing',
'rxjs',
'built',
];
varpackagesConfig={};
packages.forEach(function(packageName){
packagesConfig[packageName]={
main:'index.js',
defaultExtension:'js'
};
});
TheresultingpackagesConfigvariablerepresentstheaforementionedconfigurationobjectliteral.Italsoincludesmainentryfileanddefaultextensiondataforthebuiltfolder,whichisthefolderwheretheTypeScriptcompilerwillgosavingthetranspiledfileswhilewedevelopourapplication.
Withallthisconfigurationinplace,ourlaststepistofinallyconfigureSystemJSwiththesepathmappings,entrypoint,anddefaultfileextensionexpectedforeachpackagerepresentedbythepreviouspaths:
System.config({
map:pathMappings,
packages:packagesConfig,
});
ThisconcludesalltheconfigurationrequiredforSystemJS.Withthisinplace,wecanbothkickstartourapplicationandleveragetheES2015moduleimportsyntaxinourcode,aswewillseeinthenextsections.
www.EBooksWorld.ir
ServingtheexamplesofthisbookBeforemovingonwithourexample,weneedalocalwebservertoexecutetheexamplescontainedinthisbook.Ifyoualreadyhaveaworkingwebserverthatyoucanconfiguretopointtoyourworkingdirectory,thenskiptothenextsection.Otherwise,setupawebserverinyourworkspace.Asaneasyworkaround,werecommendyouinstalltheextraordinarilypowerfulandlightweightlite-servernodemodulefromNPM:
$npminstall-glite-server
Then,youcanrunawebserverwithlive-reloadingfunctionalitybyrunningthefollowingcommandinaterminalshellaftermovingintoyourprojectfolder:
$lite-server
Afterexecutingtheprecedingcommand,abrowserwindowwillbefired,pointingtoyourworkingdirectory.PleaserefertotheNPMmoduleofficialrepositoryinordertocheckoutalltheoptionsavailable(https://github.com/johnpapa/lite-server).
Tip
Itisactuallyrecommendedtoinstallthelite-serverpackagepairedupwiththetypescriptandconcurrentlypackages,allofthemasdevelopmentdependenciesinstalledwiththe--save-devflag.Thisway,youcanruntheTypeScriptcompilerinwatchmodeandthelocalserveratthesametimewithasinglecommandthatcanbewrappedinthestartscriptofpackage.json.Then,youcanstartbuildingstuffrightawaybyaccessingyourworkingfolderandexecutingnpmstart.Thisbook'scoderepositoryinGitHubimplementsthisapproach,sofeelfreetoborrowthepackage.jsonexampleforyourconvenience.
www.EBooksWorld.ir
PuttingeverythingtogetherOurHTMLfileisnowreadytohostourAngular2component.Todoso,let'seditthetemplateagainanddropacustomelementwiththesametagnamewedefinedintheselectorpropertyofourcomponent.Then,importtheactualfilethatcontainsthecomponentclassdeclaration,leveragingtheAPIofSystemJSforthat.Checkoutthesechangesinthefollowingexample:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>HelloAngular2!</title>
<scriptsrc="node_modules/es6-shim/es6-shim.min.js"></script>
<scriptsrc="node_modules/zone.js/dist/zone.js"></script>
<scriptsrc="node_modules/reflect-metadata/Reflect.js"></script>
<scriptsrc="node_modules/systemjs/dist/system.js"></script>
<scriptsrc="node_modules/rxjs/bundles/Rx.js"></script>
<scriptsrc="systemjs.config.js"></script>
//Hereweimportthecomponentmodule
//withnofileextension
System.import('built/hello-angular');
</script>
</head>
<body>
<!--Thisisourcustomelementtag-->
<hello-angular></hello-angular>
</body>
</html>
Howcoolisthat?Now,wecancreateourowncustomelementsthatrenderwhateverwedefineinthem.Let'sbringupthepageinourwebserverandseeitinactionbygoingtohttp://localhost:3000/(orwhateverhostandportyourlocalwebserveroperatesin).
Unfortunately,ifwereloadthebrowser,nothingwillhappenandwewillonlyseeablankpagewithnothinginthere.ThisisbecausewestillneedtobootstrapourcomponenttoinstantiateitontheHTMLpage.
Let'sreturntoourcomponentfilehello-angular.tsandaddafinallineofcode:
import{Component}from'@angular/core';
import{bootstrap}from'@angular/platform-browser-dynamic';
@Component({
selector:'hello-angular',
template:'<h1>{{greeting}}</h1>'
})
classHelloAngularComponent{
greeting:string;
constructor(){
this.greeting='HelloAngular2!';
www.EBooksWorld.ir
}
}
bootstrap(HelloAngularComponent);//Componentisbootstrapped!
Thebootstrapcommandinstancesthecontrollerclasswepassasanargumentandusesittolayoutacompleteapplicationscaffold.Basically,thebootstrapmethodkickstartsthefollowingactions:
Analyzesthecomponentconfiguredasitsfirstargumentandchecksitstype.SearchestheDOMafteranelementwithatagmatchingthecomponentselector.Createsachildinjectorthatwillhandletheinjectionofdependenciesinthatcomponentandallthechilddirectives(includingcomponents,whicharedirectivestoo)thatsuchacomponentmighthost,inaverysimilarwayatreehasramifications.ItcreatesanewZone.WewillnotcoverZonesinthisbook,butlet'sjustsaythatZonesareinchargeofmanagingthechangedetectionmechanismofeachinstanceofabootstrappedcomponentinanisolatedfashion.ItcreatesaShadowDOMcontextinthecustomelementidentifiedbythecomponentselectorandrenderswithintheHTMLdefinedinthecomponenttemplate.Thecomponentcontrollerclassisinstantiatedstraightawayandthechangedetectionmachineryisfired.NowthatwehavetheShadowDOMplaceholdersinplace,dataprovidersareinitiatedanddataisinjectedwhererequired.
Laterinthisbook,wewillcoverhowwecanleveragethebootstrapcommandtodisplaydebugginginformationorhowapplicationproviderscanbegloballyoverriddenthroughoutthewholeapplicationsothedependencyinjectorbakedinAngular2pickstherightdependencywhererequired.
Hopefully,youarerunningtheTypeScriptcompilerinwatchmode.Otherwise,pleaseexecutethetsccommandtotranspileourcodetoES5andreloadthebrowser.WecandelightourselveswiththerenderedcontentofourveryfirstAngular2component.Yay!
www.EBooksWorld.ir
www.EBooksWorld.ir
EnhancingourIDEBeforemovingonwithourjourneythroughAngular2,it'stimetotakealookatIDEstoo.OurfavoritecodeeditorcanbecomeanunparalleledallywhenitcomestoundertakinganagileworkflowentailingTypeScriptcompilationatruntime,statictypecheckingandintrospection,andcodecompletionandvisualassistancefordebuggingandbuildingourapp.Thatbeingsaid,let'shighlightsomemajorcodeeditorsandtakeabird'seyeviewofhoweachoneofthemcanassistuswhendevelopingAngular2applications.Ifyou'rejusthappywithtriggeringthecompilationofyourTypeScriptfilesfromthecommandlineanddonotwanttohavevisualcodeassistance,feelfreetoskiptothenextsection.Otherwise,jumpstraighttothefollowingsectionthatcoverstheIDEofyourchoice.
www.EBooksWorld.ir
SublimeText3Thisisprobablyoneofthemostwidespreadcodeeditorsnowadays,althoughithaslostsomemomentumlatelywithusersfavoringotherrisingcompetitorssuchasGitHub'sveryownAtom.Ifthisisyoureditorofchoice,wewillassumethatit'salreadyinstalledonyoursystemandyoualsohaveNode(whichisobvious,otherwise,youcouldhavenotinstalledTypeScriptinfirstplacethroughNPM).InordertoprovidesupportforTypeScriptcodeediting,youneedtoinstallMicrosoft'sTypeScriptplugin,availableathttps://github.com/Microsoft/TypeScript-Sublime-Plugin.Pleaserefertothispagetolearnhowtoinstallthepluginandalltheshortcutsandkeymappings.
Oncesuccessfullyinstalled,itonlytakesCtrl+spacebartodisplaycodehintsbasedontypeintrospection(seethefollowingscreenshot).Ontopofthat,wecantriggerthebuildprocessandcompilethefiletotheJavaScriptweareworkingonbyhittingtheF7functionkey.Realtimecodeerrorreportingisanotherfancyfunctionalityyoucanenablefromthecommandmenu.
www.EBooksWorld.ir
AtomDevelopedbyGitHub,thehighlycustomizableenvironmentandeaseofinstallationofnewpackageshasturnedAtomintotheIDEofchoiceforalotofpeople.ItisworthmentioningthatthecodeexamplesprovidedinthisbookwereactuallycodedusingAtomonly.
InordertooptimizeyourexperiencewithTypeScriptwhencodingAngular2apps,youneedtoinstalltheAtomTypeScriptpackage.YoucaninstallitbymeansoftheAPMCLIorjustusethebuilt-inpackageinstaller.ThefunctionalitiesincludedareprettymuchthesameaswehaveinSublimeafterinstallingtheMicrosoftpackage:automaticcodehints,statictypechecking,codeintrospection,orautomaticbuilduponsavetonameafew.Ontopofthat,thispackagealsoincludesaconvenientbuilt-intsconfig.jsongenerator.
www.EBooksWorld.ir
VisualStudioCodeVisualStudioCode,arelativelynewcodeeditorbackedbyMicrosoft,isgainingmomentumasaseriouscontenderintheAngular2medium,mostlybecauseofitsgreatsupportforTypeScriptoutofthebox.TypeScripthasbeen,toagreaterextent,aprojectdrivenbyMicrosoft,soitmakessensethatoneofitspopulareditorswasconceivedwithbuilt-insupportforthislanguage.Thismeansthatallthenicefeatureswemightwantarealreadybakedin,includingsyntaxanderrorhighlightingandautomaticbuilds.
www.EBooksWorld.ir
WebStormThisexcellentcodeeditorsuppliedbyIntelliJisalsoagreatpickforcodingAngular2appsbasedonTypeScript.TheIDEcomeswithbuilt-insupportforTypeScriptoutoftheboxsothatwecanstartdevelopingAngular2componentsfromdayone.WebStormalsoimplementsabuilt-intranspilerwithsupportforfilewatching,sowecancompileourTypeScriptcodeintopurevanillaJavaScriptwithoutrelyingonanythird-partyplugins.
www.EBooksWorld.ir
LeveragingGulpwithotherIDEsMaybeyourIDEisnotlistedhereandyoudonotwanttoswitchfromyourfavoritecodeeditornow,nothavingthechance,forwhateverreason,toautomateTypeScriptcompilationforyourproject.OrperhapsyoudonotfeelverycomfortablemessingaroundwiththeTypeScriptcommandsontheconsole.
Ifthisisnotthecase,feelfreetoskiptothenextsection.However,ifyourelatetothiscasescenario,don'tworry.ModernJavaScripttaskrunnershaveyourback.Let'spickGulp(http://gulpjs.com)andseehowwecancreateasupersimplescripttoautomateTypeScriptcompilationinourproject.
First,let'sproceedwiththedependenciesinstallation.Basically,wewillinstallGulpandthengulp-typescript,atypescriptcompilerforgulpwithincrementalcompilationsupport.Onyourconsolewindow,typethefollowingcommands:
$npminstall-ggulp
$npminstallgulpgulp-typescript--save-dev
Let'screateaJavaScriptfilenamedgulpfile.jsattherootofyourprojectwiththefollowingcontent:
vargulp=require('gulp');
varts=require('gulp-typescript');
vartsProject=ts.createProject('tsconfig.json');
gulp.task('build',function(){
vartsResult=tsProject.src().pipe(ts(tsProject));
returntsResult.js.pipe(gulp.dest('./built'));
});
Youwillneedatsconfig.jsonfileattherootofyourproject,soourGulptaskcanfetchourcompilationpreferencesfromit.Fromthismomentonwards,wecanlaunchthebuildprocessingoverthefileslistedatthefiles'arraypropertyinourtsconfig.jsonfilebyexecutingthefollowingcommand:
$gulpbuild
Unfortunately,thegulp-typescriptplugindoesnotsupportfilewatching,soifwewanttotriggerthebuildprocessingautomaticallyeverytimeaTypeScriptfilechange,weneedtorelyonGulp'snativewatchmethod.Todoso,justaddthefollowingchunkofcodeattheendofourgulpfile.jsfile:
gulp.task('watch',['build'],function(){
gulp.watch('./**/*.ts',['build']);
});
Now,youcanlaunchthebuildprocessandwatchthefilechangesbyexecutingthefollowingcommand:
www.EBooksWorld.ir
$gulpwatch
www.EBooksWorld.ir
DivingdeeperintoAngular2componentsWehavecomealongwaynow,fromtappingonTypeScriptforthefirsttimetolearninghowtocodethebasicscriptingschemaofanAngular2component.However,beforejumpingontomoreabstracttopics,let'sfleshoutourcurrentcomponentwithmorefunctionalitiesandtakeanoverviewofthemostcommontraitsofAngularappsandcomponents.
www.EBooksWorld.ir
ImprovingproductivitySometimes,weneedsomehelperstoboostourfocus,especiallywhenwedealwithtooabstractstuffthatrequiresadditionalattention.AwidelyacceptedapproachisthePomodorotechnique,inwhichweputtogetheratasklistandthensplitthedeliverablesintoto-doitemsthatwon'trequireusmorethan25minutestoaccomplish.Whenwepickanyofthoseto-doitems,wefocusunderdistraction-freemodeonitsdeliveryfor25minuteswiththehelpofacountdowntimer.Youcangrabmoreinformationaboutthistechniqueathttp://pomodorotechnique.com.
Inthisbook,wearegoingtobuildamajorcomponentthatrepresentsthisfunctionalityandfillthecomponentwithadditionalfunctionalitiesandUIitemswrappedinsidetheirowncomponents.Todoso,wewillusethePomodorotechnique,solet'sstartbycreatingaPomodorotimer.
www.EBooksWorld.ir
ComponentmethodsanddataupdatesCreateanewpomodoro-timer.tsfileinthesamefolderandpopulateitwiththefollowingbasicimplementationofaverysimplecomponent.Don'tworryabouttheaddedcomplexity,wewillrevieweachandeverychangemadeafterthecodeblock:
import{Component}from'@angular/core';
import{bootstrap}from'@angular/platform-browser-dynamic';
@Component({
selector:'pomodoro-timer',
template:'<h1>{{minutes}}:{{seconds}}</h1>'
})
classPomodoroTimerComponent{
minutes:number;
seconds:number;
constructor(){
this.minutes=24;
this.seconds=59;
}
}
bootstrap(PomodoroTimerComponent);
Ournewcomponentis,infairness,notthatmuchdifferentfromtheonewepreviouslyhad.Weupdatedthenamestomakethemmoreself-descriptiveandthendefinedtwopropertyfields,staticallytypedasnumbersinourPomodoroTimerComponentclass.Thesearerenderedinthecontainedview,wrappedinsidean<h1>element.Now,opentheindex.htmlfileandreplacethe<hello-angular></hello-angular>customelementwithournew<pomodoro-timer></pomodoro-timer>tag.Youcanduplicateindex.htmlandsaveitunderadifferentnameifyoudonotwanttoloosetheHTMLsideofourfancy"HelloWorld"component.
Note
Anoteaboutnamingcustomelements
SelectorsinAngular2arecasesensitive.Aswewillseelaterinthisbook,componentsareasubsetofdirectivesthatcansupportawiderangeofselectors.Whencreatingcomponents,wearesupposedtosetacustomtagnameintheselectorpropertybyenforcingadash-casingnamingconvention.Whenrenderingthattaginourview,weshouldalwaysclosethetagasanon-voidelement.So<custom-element></custom-element>iscorrect,while<custom-element/>willtriggeranexception.Lastbutnotleast,certain"common"camelcasenamesmightconflictwiththeAngular2implementation,soavoidnameslikeAppVieworAppElement.
YouwillwanttoupdatethereferenceinyourSystem.import(...)blockatindex.htmltopointtoournewcomponentaswell:
www.EBooksWorld.ir
System.import('built/pomodoro-timer').then(null,console.error.bind(console));
Now,itisagoodtimetomentionthattheimportmethodofSystemJSisasynchronousandreturnsapromiseoncethemodulehasbeensuccessfullyloaded.Wecanleveragethispromisetothrowanyeventualerrormessagetotheconsole,whichwillbecomequitehandywheneverwehavetodebugourcode.Youwillseethispracticelaterinthisbook.
Ifyoubringupabrowserwindowandloadthisfile,youwillseearepresentationofthenumbersdefinedinthecomponentclass.Butwewanttodomorethanjustdisplayahandfulofnumbers,right?Weactuallywantthemtorepresentatimecountdown,andwecanachievethatbyintroducingthesechanges.Let'sfirstintroduceafunctionwecaniterateoninordertoupdatethecountdown.Addthisfunctionaftertheconstructorfunction:
tick():void{
if(--this.seconds<0){
this.seconds=59;
if(--this.minutes<0){
this.minutes=24;
this.seconds=59;
}
}
}
Asyoucanseehere,functionsinTypeScriptneedtobeannotatedwiththetypeofthevaluetheyreturn,orjustvoidifnone.Ourfunctionassessesthecurrentvalueofbothminutesandseconds,andtheneitherdecreasestheirvalueorjustresetsittotheinitialvalue.Thenthisfunctioniscalledeverysecondbytriggeringatimeintervalfromtheclassconstructor:
constructor(){
this.minutes=24;
this.seconds=59;
setInterval(()=>this.tick(),1000);
}
Here,wespotforthefirsttimeinourcodeanarrowfunction(alsoknownasalambdafunction,fatarrow,andsoon),anewsyntaxforfunctionsbroughtbyECMAScript6thatwewillcoverinmoredetailinChapter2,IntroducingTypeScript.Thetickfunctionisalsomarkedasprivate,soitcannotbeinspectedorexecutedoutsideaPomodoroTimerComponentobjectinstance.
Sofarsogood!WehaveaworkingPomodorotimerthatcountdownsfrom25minutesto0,andthenstartsalloveragain.Theproblemisthatwearereplicatingcodehereandthere.So,let'srefactoreverythingalittlebittopreventcodeduplication.
constructor(){
this.resetPomodoro();
setInterval(()=>this.tick(),1000);
}
resetPomodoro():void{
www.EBooksWorld.ir
this.minutes=24;
this.seconds=59;
}
privatetick():void{
if(--this.seconds<0){
this.seconds=59;
if(--this.minutes<0){
this.resetPomodoro();
}
}
}
Wehavewrappedtheinitialization(andreset)ofminutesandsecondsinsideourfunctionresetPomodoro,whichiscalleduponinstantiatingthecomponentorreachingtheendofthecountdown.Waitamomentthough!AccordingtothePomodorotechnique,pomodoropractitionersareallowedtorestinbetweenpomodorosorevenpausethemshouldanunexpectedcircumstancegetsontheway.Weneedtoprovidesomesortofinteractivitysotheusercanstart,pause,andresumethecurrentpomodorotimer.
www.EBooksWorld.ir
AddinginteractivitytothecomponentAngular2providesatop-notchsupportforeventsthroughadeclarativeinterfacethatremindstheoneinotherframeworkssuchasReact.Let'sfirstmodifyourtemplatedefinition:
@Component({
selector:'pomodoro-timer',
template:`
<h1>{{minutes}}:{{seconds}}</h1>
<p>
<button(click)="togglePause()">
{{buttonLabel}}
</button>
</p>
`
})
Weusedamultilinetextstring!ECMAScript6introducedtheconceptoftemplatestrings,whicharestringliteralswithsupportforembeddedexpressions,interpolatedtextbindings,andmultilinecontent.WewilllookintotheminmoredetailinChapter2,IntroducingTypeScript.
Inthemeantime,justfocusonthefactthatweintroducedanewchunkofHTMLthatcontainsabuttonwithaneventhandlerthatlistenstoclickeventsandexecutesthetogglePausemethoduponclicking.This(click)attributeissomethingyoumightnothaveseenbefore,eventhoughitisfullycompliantwiththeW3Cstandards.Again,wewillcoverthisinmoredetailinChapter3,ImplementingPropertiesandEventsinOurComponents.Let'sfocusonthetogglePausemethodandthenewbuttonLabelbinding.First,let'smodifyourclasspropertiessothattheylooklikethis:
classPomodoroTimerComponent{
minutes:number;
seconds:number;
isPaused:boolean;
buttonLabel:string;
//…Restofcodewillremainasitisbelowthispoint
}
Weintroducedtwonewfields.FirstisbuttonLabelthatcontainsthetextthatwillbelaterondisplayedonournewly-createdbutton.isPausedisanewly-createdvariablethatwillassumeatrue/falsevalue,dependingonthestateofourtimer.So,wemightneedaplacetotogglethevalueofsuchafield.Let'screatethetogglePausemethodwementionedearlier:
togglePause():void{
this.isPaused=!this.isPaused;
//ifcountdownhasstarted
if(this.minutes<24||this.seconds<59){
this.buttonLabel=this.isPaused?'Resume':'Pause';
}
}
www.EBooksWorld.ir
Inanutshell,thetogglePausemethodjustswitchesthevalueofisPausedtoitsoppositeandthen,dependingonsuchnewvalueandwhetherthetimerhasstarted(whichwouldentailthatanyofthetimevariableshasavaluelowerthantheinitializationvalue)ornot,weassignadifferentlabeltoourbutton.
Now,weneedtoinitializethesevalues,anditseemsthereisnobetterplaceforit.So,theresetPomodorofunctionistheplacewherevariablesaffectingthestateofourclassareinitialized:
resetPomodoro():void{
this.minutes=24;
this.seconds=59;
this.buttonLabel='Start';
this.togglePause();
}
ByexecutingtogglePauseeverytime,weresetthePomodorotomakesurethatwheneverthePomodororeachesastatewhereitrequirestobereset,thecountdownbehaviorwillswitchtotheoppositestateithadpreviously.Thereisonlyonetweakleftinthecontrollermethodthathandlesthecountdown:
privatetick():void{
if(!this.isPaused){
this.buttonLabel='Pause';
if(--this.seconds<0){
this.seconds=59;
if(--this.minutes<0){
this.resetPomodoro();
}
}
}
}
Obviously,wedonotwantthecountdowntocontinuewhenthetimerissupposedtobepaused,sowewrapthewholescriptinaconditional.Inadditiontothis,wewillwanttodisplayadifferenttextonourbuttonwheneverthecountdownisnotpausedandonceagainwhenthecountdownreachesitsend,stoppingandthenresettingthepomodorotoitsinitialvalueswillbetheexpectedbehavior.ThisreinforcestheneedofinvokingthetogglePausefunctionwithinresetPomodoro.
www.EBooksWorld.ir
ImprovingthedataoutputintheviewandpolishingtheUISofar,wereloadedthebrowserandplayedaroundwiththenewlycreatedtogglefeature.However,thereisapparentlysomethingthatstillrequiressomepolishing:whenthesecondscounterislessthan10,itdisplaysasingle-digitnumberinsteadoftheusualtwo-digitnumbersweareusedtoseeindigitalclocksandwatches.Luckily,Angular2implementsasetofdeclarativehelpersthatformatthedataoutputinourtemplates.WecallthemPipes,andwewillcoverthemindetaillaterinChapter3,ImplementingPropertiesandEventsinOurComponents.Forthetimebeing,let'sjustintroducethenumberpipeinourcomponenttemplateandconfigureittoformatthesecondsoutputtodisplaytwodigitsallthetimes.Updateourtemplatesothatitlookslikethis:
@Component({
selector:'pomodoro-timer',
template:`
<h1>{{minutes}}:{{seconds|number:'2.0'}}</h1>
<p>
<button(click)="togglePause()">
{{buttonLabel}}
</button>
</p>
`
})
Basically,weappendedthepipenametotheinterpolatedbindinginourtemplateseparatedbyapipe(|)symbol,hencethename.Reloadthetemplateandyouwillseehowthesecondsfigurealwaysdisplaystwodigits,regardlessofthevalueitassumes.
WehavecreatedafullyfunctionalPomodoroTimerwidgetthatwecanreuseorembedinmorecomplexapplications.Chapter5,BuildinganApplicationwithAngular2Components,willguideusthroughtheprocessofembeddingandnestingourcomponentsinthecontextoflargercomponenttrees.
Inthemeantime,let'saddsomeUIbeautificationtomakeourcomponentmoreappealing.WealreadyintroducedaclassattributeinourbuttontagasananticipationoftheimplementationoftheBootstrapCSSframeworkinourproject.Let'simporttheactualstylesheetwedownloadedthroughNPMwheninstallingtheprojectdependencies.Openpomodoro-timer.htmlandaddthissnippetattheendofthe<HEAD>element:
<linkrel="stylesheet"href="node_modules/bootstrap/dist/css/bootstrap.min.css">
Now,let'sbeautifyourUIbyinsertinganicepageheaderrightbeforeourcomponent:
<body>
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroTimer</strong>
www.EBooksWorld.ir
</div>
</div>
</nav>
<pomodoro-timer></pomodoro-timer>
</body>
TweakingthecomponentbuttonwithaBootstrapbuttonclasswillgiveitmorepersonalityandwrappingthewholetemplateinacenteringcontainerandappendinganiceiconatthetopwilldefinitelycompounduptheUI.Solet'supdatethetemplateinourtemplatetolooklikethis:
<divclass="text-center">
<imgsrc="assets/img/pomodoro.png"alt="Pomodoro">
<h1>{{minutes}}:{{seconds|number:'2.0'}}</h1>
<p>
<button(click)="togglePause()"
class="btnbtn-danger">
{{buttonLabel}}
</button>
</p>
</div>
Fortheicon,wepickedabitmapicondepictingapomodoro.Youcanuseanybitmapimageofyourchoiceoryoucanjustskiptheiconfornow,eventhoughwewillneedanactualpomodoroiconintheforthcomingchapters.ThisishowourPomodorotimerapplooksafterimplementingallthesevisualtweaks:
www.EBooksWorld.ir
Tip
Downloadingtheexamplecode
YoucandownloadtheexamplecodefilesforthisbookfromGitHubathttps://github.com/deeleman/learning-angular2.
Youcandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Youcandownloadthecodefilesbyfollowingthesesteps:
Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.HoverthemousepointerontheSUPPORT tabatthetop.ClickonCodeDownloads&Errata.EnterthenameofthebookintheSearchbox.
www.EBooksWorld.ir
Selectthebookforwhichyou'relookingtodownloadthecodefiles.Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.ClickonCodeDownload.
YoucanalsodownloadthecodefilesbyclickingontheCodeFilesbuttononthebook'swebpageatthePacktPublishingwebsite.Thispagecanbeaccessedbyenteringthebook'snameintheSearchbox.PleasenotethatyouneedtobeloggedintoyourPacktaccount.
Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:
WinRAR/7-ZipforWindowsZipeg/iZip/UnRarXforMac7-Zip/PeaZipforLinux
www.EBooksWorld.ir
SummaryWelookedatwebcomponentsaccordingtomodernwebstandardsandhowAngular2componentsprovideaneasyandstraightforwardAPItobuildourowncomponents.WecoveredTypeScriptandsomebasictraitsofitssyntaxasapreparationforChapter2,IntroducingTypeScript.WesawhowtosetupourworkingspaceandwheretogotofindthedependenciesweneedtobringTypeScriptintothegameandusetheAngular2libraryinourprojects,goingthroughtheroleofeachdependencyinourapplication.
Ourfirstcomponentgaveustheopportunitytodiscusstheformofacontrollerclasscontainingpropertyfields,constructor,andutilityfunctions,andwhymetadataannotationsaresoimportantinthecontextofAngular2applicationstodefinehowourcomponentwillintegrateitselfintheHTMLenvironmentwhereitwilllive.Now,wealsoknowhowtodeploywebservertoolsandenhanceourcodeeditorstomakeourliveseasierwhendevelopingAngular2apps,leveragingtypeintrospectionandcheckingonthego.Ourfirstwebcomponentfeaturesitsowntemplateandsuchtemplateshostpropertybindingsdeclarativelyintheformofvariableinterpolations,convenientlyformattedbypipes.Bindingeventlistenersisnoweasierthaneveranditssyntaxisstandards-compliant.
Thenextchapterwillcover,indetail,alltheTypeScriptfeaturesweneedtoknowtogetuptospeedwithAngular2innotime.
www.EBooksWorld.ir
Chapter2.IntroducingTypeScriptInthepreviouschapter,webuiltourveryfirstcomponentandweusedTypeScripttoshapethecodescripts,whichgaveformtoit.Alltheexamplesincludedinthisbookuseitssyntax.Aswewillseelaterinthisbook,writingourscriptsinTypeScriptandleveragingitsstatictypingwillgiveusaremarkableadvantageovertheotherscriptinglanguages.
ThischapterisnotathoroughoverviewoftheTypeScriptlanguage.WewilljustfocusonthecoreelementsofthelanguageandstudythemindetailonourjourneythroughAngular2.ThegoodnewsisthatTypeScriptisnotallthatcomplex,andwewillmanagetocovermostofitsrelevantparts.
Inthischapter,wewill:
LookatthebackgroundandrationalebehindTypeScriptDiscoveronlineresourcestopracticewhilewelearnRecapontheconceptoftypedvaluesandhowtorepresentthemBuildourowntypes,basedonclassesandinterfacesLearntobetterorganizeourapplicationarchitecturewithmodules
www.EBooksWorld.ir
UnderstandingthecaseforTypeScriptThenaturalevolutionoftheearlyJavaScript-drivensmallwebapplicationsintothickmonolithicclientsunveiledtheshortcomingsoftheECMAScript5JavaScriptspecification.Inanutshell,large-scaleJavaScriptapplicationssufferedfromseriousmaintainabilityandscalabilityproblemsassoonastheygrewupinsizeandcomplexity.
Thisissuebecamemorerelevantasnewlibrariesandmodulesrequiredseamlessintegrationontoourapplications.Thelackofgoodmechanismsforinteroperabilityledtoreallycumbersomesolutionsthatneverseemedtofitthebill.
Asaresponsetotheseconcerns,ECMAScript6(alsocalledasES6orES2015)promisedtosolvethesemaintainabilityandscalabilityissuesbyintroducingbettermoduleloadingfunctionalities,animprovedlanguagearchitectureforbetterhandlingofscope,andawidevarietyofsyntacticsugartobettermanagetypesandobjects.Theintroductionofclass-basedprogrammingturnedintoanopportunitytoembraceamoreOOPapproachwhenbuildinglarge-scaleapplications.
Microsofttookthechallengeandspentnearly2yearsbuildingasupersetofthelanguage,combiningtheconventionsofES6andborrowingsomeproposalsfromES7.Theideawastolaunchsomethingthathelpedoutwithbuildingenterpriseapplicationswithalowererrorfootprintbymeansofstatictypechecking,bettertooling,andcodeanalysis.
After2yearsofdevelopmentledbyAndersHejlsberg,leadarchitectofC#andcreatorofDelphiandTurboPascal,TypeScript0.8wasfinallyintroducedin2012anditreachedVersion1.0twoyearslater.TypeScriptwasnotonlyrunningaheadofECMAScript6,butitalsoimplementedthesamefeaturesandprovidedasolidenvironmentforbuildinglarge-scaleapplicationsbyintroducing,amongotherfeatures,optionalstatictypingthroughtypeannotations,therebyensuringtypecheckingatcompiletime.Thiscontributestocatchingerrorsinearlierstagesofthedevelopmentprocess.Thesupportfordeclarationfilesalsogivesdeveloperstheopportunitytodescribetheinterfaceoftheirmodules,sootherdeveloperscanbetterintegratethemintotheircodeworkflowandtooling.
www.EBooksWorld.ir
ThebenefitsofTypeScriptThefollowinginfographicprovidesabird'seyeviewofthedifferentfeaturesthatdistinguishesECMAScript6fromECMAScript5,andthendifferentiatesTypeScriptfromthetwo.
AsasupersetofECMAScript6,oneofthemainadvantagesofembracingTypeScriptinyournextprojectisthelowentrybarrier.IfyouknowECMAScript6,youareprettymuchallset,sincealltheadditionalfeaturesinTypeScriptareoptional.Youcanpickandintroduceinyourpracticethefeaturesthathelpyoutoachieveyourgoal.Allinall,thereisalonglistofstrongargumentsforadvocatingforTypeScriptinyournextprojectandallofthemobviouslyapplytoAngular2aswell.Hereisashortrundownofarguments,justtonameafew:
Annotatingourcodewithtypesensuresaconsistentintegrationofourdifferentcodeunitsandimprovescodereadabilityandcomprehension.TheTypeScript'sbuilt-intype-checkerwillanalyzeyourcodeatruntimeandhelpyoupreventerrorsevenbeforeexecutingyourcode.Theuseoftypesensuresconsistencyacrossyourapplications.Incombinationwiththeprevioustwo,theoverallcodeerrorsfootprintgetsminimizedinthelongrun.TypeScriptextendsclasseswithlongtimedemandedfeaturessuchasclassfields,privatemembers,enums,andsoon.Theuseofdecoratorsopensthedoortoextendourclassesandimplementationsin
www.EBooksWorld.ir
unparalleledways.Creatinginterfacesandtypedefinitionfiles(whichwewillnotcoverinthisbookthough)ensuresasmoothandseamlessintegrationofourlibrariesinothersystemsandcodebases.TypeScriptsupportacrossthedifferentIDEsonstoreisterrific,andwecanbenefitfromcodehighlighting,real-timetypechecking,andautomaticcompilationatnocost.TheTypeScriptsyntaxwilldefinitelypleasedeveloperscomingfromotherbackgroundssuchasJava,C#,C++,andsoon.
www.EBooksWorld.ir
IntroducingTypeScriptresourcesinthewildInthepreviouschapter,wesawhowtoinstallTypeScriptinoursystemandusethecompilerfortranspilingourTypeScriptfilesintoES5scriptfiles.Now,wearegoingtotakealookatwherecanwegetfurthersupporttolearnandtest-driveournewknowledgeofTypeScript.
TheTypeScriptofficialsite
Obviously,ourfirststopistheofficialsiteforthelanguage:http://www.typescriptlang.org.There,wecanfindamoreextensiveintroductiontothelanguageandlinkstoIDEsandcorporatesupportersofthisproject.Nevertheless,themostimportantsectionsthatwewilldefinitelyrevisitmoreoftenarethelearnsectionandtheplaysandbox.
Thelearnsectiongivesusaccesstoaquicktutorialtogetuptospeedwiththelanguageinnotime.Itmightbeinterestingasarecaponwhatwediscussedinthepreviouschapter,butwewouldsuggestyoutoskipitinfavorofthesamplepagesandthelanguagespec,thelatterbeingadirectlinktothefullextensivedocumentationofthelanguageatGitHub.Thisisa
www.EBooksWorld.ir
pricelessresourceforbothnewandexperiencedusers.
Theplaysectionoffersaconvenientsandbox,includingsomereadymadecodeexamples,coveringsomeofthemostcommontraitsofthelanguage.Weencourageyoutoleveragethistooltotestoutthecodeexampleswewillseethroughoutthischapter.
TheTypeScriptWiki
WemadeareferencetotheTypeScriptWikiinthepreviouschapterwhenspeakingaboutthemostbasicparametersweneedtoknowwhenexecutingcommandswiththeTypeScriptcompilerAPI.
ThecodeforTypeScriptisfullyopensourcedatGitHub,andtheMicrosoftteamhasmadeagoodeffortatdocumentingthedifferentfacetsofthecodeintheWikiavailableontherepositorysite.Weencourageyoutogotakealookatitanytimeyouhaveaquestionorwanttodelvedeeperintoanyofthelanguagefeaturesorformaspectsofitssyntax.
TheWikiislocatedathttps://github.com/Microsoft/TypeScript/wiki.
www.EBooksWorld.ir
TypesinTypeScriptWorkingwithTypeScriptoranyothercodinglanguagemeansbasicallyworkingwithdata,andsuchdatacanrepresentdifferentsortsofcontent.Thisiswhatweknowastypes,anounusedtorepresentthefactthatsuchdatacanbeatextstring,anintegervalue,oranarrayofthesevaluetypes,amongothers.ThisisnothingnewtoJavaScript,sincewehavealwaysbeenworkingimplicitlywithtypes,butinaflexiblemanner.Thismeansthatanygivenvariablecouldassume(orreturninthecaseoffunctions)anytypeofvalue.Sometimes,thisledtoerrorsandexceptionsinourcodebecauseoftypecollisionsbetweenwhatourcodereturnedandwhatweexpectedittoreturntype-wise.Whilethisflexibilitycanstillbeenforcedbymeansofanytypethatwewillseelateroninthischapter,staticallytypingourvariablesgivesusandourIDEsagoodpictureofwhatkindofdatawearesupposedtofindoneachinstanceofcode.Thisbecomesaninvaluablewaytohelpdebugourapplicationsatcompiletimebeforeit'stoolate.
www.EBooksWorld.ir
StringProbablyoneofthemostwidelyusedprimitivetypesinourcodewillbethestringtype,wherewepopulateavariablewithapieceoftext:
varbrand:string='Chevrolet';
Checkoutthetypeassignationnexttothevariablename,whichisseparatedbyacolonsymbol.ThisishowweannotatetypesinTypeScript,aswealreadysawinthepreviouschapter.
Backtothestringtype,wecanuseeithersingleordoublequotes,anditissameasECMAScript6.Wecandefinemultilinetextstringswithsupportfortextinterpolationwithplaceholdervariablesbyusingthesametype:
varbrand:string='Chevrolet';
varmessage:string=`Todayit'sahappyday!
Ijustboughtanew${brand}car`;
DeclaringourvariablestheECMAScript6way
TypeScript,asasupersetofECMAScript6,supportsexpressivedeclarationnounssuchaslet,whichinformsusthatthevariableisscopedtothenearestenclosingblock(eitherafunctionforlooporanyenclosingstatement).Ontheotherhand,constisanindicatorthatthevaluesdeclaredthiswayaremeanttofeaturealwaysthesametypeorvalueoncepopulated.Fortherestofthischapter,wewillenforcethetraditionalvarnotationfordeclaringvariables,butrememberthat
www.EBooksWorld.ir
NumberNumberisprobablytheothermostwidespreadprimitivedatatypealongwithstringandboolean.ThesameasinJavaScript,numberdefinesafloatingpointnumber.Thenumbertypealsodefineshexadecimal,decimal,binary,andoctalliterals:
varage:number=7;
varheight:number=5.6;
Boolean
ThebooleantypedefinesdatathatcanbeTrueorFalse,representingthefulfillmentofacondition:
varisZeroGreaterThanOne:boolean=false;
Array
AssigningwrongmembertypestoarraysandhandlingexceptionsthatarisebythatcanbenoweasilyavoidedwiththeArraytype,wherewedescribeanarraycontainingcertaintypesonly.Thesyntaxjustrequiresthepostfix[]inthetypeannotation,asfollows:
varbrands:string[]=['Chevrolet','Ford','GeneralMotors'];
varchildrenAges:number[]=[8,5,12,3,1];
IfwetrytoaddanewmembertothechildrenAgesarraywithatypeotherthannumber,theruntimetypecheckerwillcomplain,makingsureourtypedmembersremainconsistentandourcodeiserror-free.
www.EBooksWorld.ir
DynamictypingwiththeanytypeSometimes,itishardtoinferthedatatypeoutoftheinformationwehaveatsomepoint,especiallywhenweareportinglegacycodetoTypeScriptorintegratinglooselytypedthird-partylibrariesandmodules.Don'tworry,TypeScriptsuppliesuswithaconvenienttypeforthesecases.Theanytypeiscompatiblewithalltheotherexistingtypes,sowecantypeanydatavaluewithitandassignanyvaluetoitlateron.Thisgreatpowercomeswithagreatresponsibilitythough.Ifwebypasstheconvenienceofstatictypechecking,weareopeningthedoortotypeerrorswhenpipingdatathroughourmodules,anditwillbeuptoustoensuretypesafetythroughoutourapplication:
vardistance:any;
//Assigningdifferentvaluetypesisperfectlyfine
distance='1000km';
distance=1000;
//Allowsustoseamlesslycombinedifferenttypes
vardistances:any[]=['1000km',1000];
ThenullandundefinedJavaScriptliteralsrequirespecialmention.Inanutshell,theyaretypedundertheanytype.Thismakesitpossiblelaterontoassigntheseliteralstoanyothervariable,regardlessitsoriginaltype.
Enum
Enumisbasicallyasetofuniquenumericvaluesthatwecanrepresentbyassigningfriendlynamestoeachoneofthem.Theuseofenumsgoesbeyondassigninganaliastoanumber.Wecanusethemasawaytolist,inaconvenientandrecognizableway,thedifferentvariationsthataspecifictypecanassume.
Enumsaredeclaredusingtheenumkeyword,withoutvaroranyothervariabledeclarationnoun,andtheybeginnumberingmembersstartingat0unlessexplicitnumericvaluesareassignedtothem:
enumBrands{Chevrolet,Cadillac,Ford,Buick,Chrysler,Dodge};
varmyCar:Brands=Brands.Cadillac;
InspectingthevalueofmyCarwillreturn1(whichistheindexheldbyCadillacintheenum).Aswementionedalready,wecanassigncustomnumericvaluesintheenum:
enumBrandsReduced{Tesla=1,GMC,Jeep};
varmyTruck:BrandsReduced=BrandsReduced.GMC;
InspectingmyTruckwillyield2,sincethefirstenumeratedvaluewassetas1already.Wecanextendvalueassignationtoalltheenummembersaslongassuchvaluesareintegers:
enumStackingIndex{
None=0,
www.EBooksWorld.ir
Dropdown=1000,
Overlay=2000,
Modal=3000
};
varmySelectBoxStacking:StackingIndex=LayerStackingIndex.Dropdown;
Onelasttrickworthmentioningisthepossibilitytolookuptheenummembermappedtoagivennumericvalue:
enumBrands{Chevrolet,Cadillac,Ford,Buick,Chrysler,Dodge};
varmyCarBrandName:string=Brands[1];
www.EBooksWorld.ir
VoidThevoidtypedefinitelyrepresentstheabsenceofanytypeanditsuseisconstrainedtoannotatingfunctionsthatdonotreturnanactualvalue.Therefore,thereisnoreturntypeeither.Wealreadyhadthechancetoseethiswithanactualexampleinthepreviouschapter:
resetPomodoro():void{
this.minutes=24;
this.seconds=59;
}
www.EBooksWorld.ir
TypeinferenceTypingourdataisoptionalsinceTypeScriptissmartenoughtoinferthedatatypeofourvariablesandfunctionreturnvaluesoutofcontextwithacertainlevelofaccuracy.Whennotypeinferenceispossible,TypeScriptwillassignthedynamicanytypetothelooselytypeddataatthecostofreducingtypecheckingtoabareminimum.
However,itisalwaysagoodpracticetoensurethattheinformationwehandleisconvenientlydescribedbyexplicitlyannotatingitstype,sowecanharnessthebenefitofhavingthecompilerverifyingcorrectnessthroughoutourcode.
www.EBooksWorld.ir
Functions,lambdas,andexecutionflowThesameasinJavaScript,functionsaretheprocessingmachineswhereweanalyzeinput,digestinformation,andapplythenecessarytransformationstothedataprovidedtoeithertransformthestateofourapplicationorreturnanoutputthatwillbeusedtoshapeourapplication'sbusinesslogicoruserinteractivity.
FunctionsinTypeScriptarenotthatdifferentfromregularJavaScript,exceptforthefactthatfunctions,justaseverythingelseinTypeScript,canbeannotatedwithstatictypesandthus,theybetterinformthecompileroftheinformationtheyexpectintheirsignatureandthedatatypetheyaimtoreturn,ifany.
www.EBooksWorld.ir
AnnotatingtypesinourfunctionsThefollowingexampleshowcaseshowaregularfunctionisannotatedinTypeScript:
functionsayHello(name:string):string{
return'Hello,'+name;
}
WecanclearlyseetwomaindifferencesfromtheusualfunctionsyntaxinregularJavaScript.First,weannotatewithtypeinformationtheparametersdeclaredinthefunctionsignature.Thismakessensesincethecompilerwillwanttocheckwhetherthedataprovidedwhenexecutingthefunctionholdsthecorrecttype.Inadditiontothis,wealsoannotatethetypeofthereturningvaluebyaddingthepostfixstringtothefunctiondeclaration.Inthesecases,wherethegivenfunctiondoesnotreturnanyvalue,thetypeannotationvoidwillgivethecompilertheinformationitrequirestoprovideapropertypechecking.
Aswementionedintheprevioussection,theTypeScriptcompilerissmartenoughtoinfertypeswhennoannotationisprovided.Inthiscase,thecompilerwilllookintotheargumentsprovidedandthereturnstatementstoinferareturningtypefromit.
FunctionsinTypeScriptcanalsoberepresentedasexpressionsofanonymousfunctions,wherewebindthefunctiondeclarationtoavariable:
varsayHello=function(name:string):string{
return'Hello,'+name;
};
However,thereisadownsideofthissyntax.Althoughtypingfunctionexpressionsthiswayisallowed,thankstotypeinference,thecompilerismissingthetypedefinitioninthedeclaredvariable.Wemightassumethattheinferredtypeofavariablethatpointstoafunctiontypedasstringisobviouslyastring.Well,it'snot.Avariablethatpointstoananonymousfunctionoughttobeannotatedwithafunctiontype.Basically,thefunctiontypeinformsaboutboththetypesexpectedinthefunctionpayloadandthetypereturnedbythefunctionexecution,ifany.Thiswholeblock,intheformof(arguments:type)=>returnedtype,becomesthetypeannotationourcompilerexpects:
varsayHello:(name:string)=>string=function(name:string):string{
return'Hello,'+name;
};
Whysuchacumbersomesyntax,youmightask?Sometimes,wewilldeclarevariablesthatmightdependonfactoriesorfunctionbindings.Then,itisalwaysagoodpracticetoprovideasmuchinformationtothecompileraswecan.Thissimpleexamplemighthelpyoutounderstandbetter:
//Twofunctionswiththesametypingbutdifferentlogic
functionsayHello(input:string):string{
return'Hello'+input;
www.EBooksWorld.ir
}
functionsayHi(input:string):string{
return'Hi'+input;
}
//Herewedeclarethevariablewithitsfunctiontype
vargreetMe:(name:string)=>string;
//Last,weassignafunctiontothevariable
greetMe=sayHello;
Thisway,wealsoensurethatlaterfunctionassignationsconformtothetypeannotationssetwhendeclaringvariables.
www.EBooksWorld.ir
FunctionparametersinTypeScriptDuetothetypecheckingperformedbythecompiler,functionparametersrequirespecialattentioninTypeScript.
Optionalparameters
ParametersareacorepartofthetypecheckingappliedbytheTypeScriptcompiler.Inotherwords,wecannotdeclareparametersandthendonotincludedinthepayloadwhenexecutingthefunctionafterwards.Thesameappliestoeventothoseargumentsinthefunctionpayload,whichwerenotoriginallydeclaredandannotatedwhendefiningthefunction.Weobviouslyneedawaytocopewiththiscasescenario,soTypeScriptoffersthisfunctionalitybyaddingthe?symbolasapostfixtotheparameternamewewanttomakeoptional:
functiongreetMe(name:string,greeting?:string):string{
if(!greeting){
greeting='Hello';
}
returngreeting+','+name;
}
Note
Whenaparameterismarkedasoptionalandnotprovidedwhenexecutingthefunction,TypeScriptwillassignthenullvaluetoit.Ontheotherhand,theruleofthumbistoputrequiredparametersfirstandthenoptionalparameterslast.
Defaultparameters
TypeScriptgivesusanotherfeaturetocopewiththescenariodepictedearlierintheformofdefaultparameters,wherewecansetadefaultvaluetheparameterwillassumewhennotexplicitlypopulateduponexecutingthefunction.Thesyntaxisprettystraightforwardaswecanseewhenwerefactorthepreviousexamplehere:
functiongreetMe(name:string,greeting:string='Hello'):string{
returngreeting+','+name;
}
Justaswithoptionalparameters,defaultparametersmustbeputrightafterthenon-defaultparametersinthefunctionsignature.
Restparameters
OneofthebigadvantagesoftheflexibilityofJavaScriptwhendefiningfunctionsisthefunctionalitytoacceptanunlimitednon-declaredarrayofparametersintheformoftheargumentsobject.InastaticallytypedcontextsuchasTypeScript,thismightbenotpossible,butitactuallyisbymeansoftheRestparameter'sobject.Here,wecandefine,attheendoftheargumentslist,anadditionalparameterprefixedbyellipsisandtypedasanarray:
www.EBooksWorld.ir
functiongreetPeople(greeting:string,...names:string[]):string{
returngreeting+','+names.join('and')+'!';
}
alert(greetPeople('Hello','John','Ann','Fred'));
Note
It'simportanttonotethattheRestparametersmustbeputattheendoftheargumentslistandcanbeleftoffwhenevernotrequireduponexecutingthefunction.
Overloadingthefunctionsignature
MethodandfunctionoverloadingisacommonpatterninotherlanguagessuchasC#.However,implementingthisfunctionalityinTypeScriptclasheswiththefactthatJavaScript,whichTypeScriptismeanttocompileto,doesnotimplementanyelegantwaytointegratethisfunctionalityoutofthebox.So,theonlyworkaroundpossiblyrequireswritingfunctiondeclarationsforeachoftheoverloadsandthenwritingageneral-purposefunctionthatwillwraptheactualimplementationandwhoselistoftypedargumentsandreturningtypesarecompatiblewithalltheothers:
functionhello(name:string):string;
functionhello(names:string[]):string;
functionhello(names:any,greeting?:string):string{
varnamesArray:string[];
if(Array.isArray(names)){
namesArray=names;
}else{
namesArray=[names];
}
if(!greeting){
greeting='Hello';
}
returngreeting+','+namesArray.join('and')+'!';
}
Intheprecedingexample,weareexposingthreedifferentfunctionsignaturesandeachofthemfeaturesdifferenttypeannotations.Wecouldevendefinedifferentreturningtypesiftherewasacaseforthat.Fordoingso,weshouldhavejustannotatedthewrappingfunctionwithananyreturntype.
www.EBooksWorld.ir
BetterfunctionsyntaxandscopehandlingwithlambdasECMAScript6introducedtheconceptoffatarrowfunctions(alsocalledlambdafunctionsinotherlanguagessuchasPython,C#,Java,orC++)asawaytobothsimplifythegeneralfunctionsyntaxandalsotoprovideabulletproofwaytohandlethescopeofthefunctionsthataretraditionallyhandledbytheinfamousscopeissuesoftacklingwiththethiskeyword.
Thefirstimpressionisitsminimalisticsyntax,where,mostofthetime,wewillseearrowfunctionsassingle-line,anonymousexpressions:
vardouble=x=>x*2;
Thefunctioncomputesthedoubleofagivennumber,x,andreturnstheresult,althoughwedonotseeanyfunctionorreturnstatementsintheexpression.Ifthefunctionsignaturecontainsmorethanoneargument,wejustneedtowrapthemallbetweenbraces:
varadd=(x,y)=>x+y;
Thismakesthissyntaxextremelyconvenientwhendevelopingfunctionaloperationssuchasmap,reduce,andothers:
varreducedArray=[23,5,62,16].reduce((a,b)=>a+b,0);
Arrowfunctionscanalsocontainstatements.Inthatcase,wewillwanttowrapthewholeimplementationincurlybraces:
varaddAndDouble=(x,y)=>{
varsum=x+y;
returnsum*2;
}
Still,whatdoesthishavetodowithscopehandling?Basically,thevalueofthiscanpointtoadifferentcontext,dependingonwhereweexecutethefunction.Thisisabigdealforalanguagethatpridesitselfonanexcellentflexibilityforfunctionalprogramming,wherepatternssuchascallbacksareparamount.Whenreferringtothisinsideacallback,welosetrackoftheuppercontextandthatusuallyforcesustouseconventionssuchasassigningthevalueofthistoavariablenamedselforthat,whichwillbeusedlateronwithinthecallback.Statementscontainingintervalortimeoutfunctionsmakeaperfectexampleofthis:
functiondelayedGreeting(name):void{
this.name=name;
this.greet=function(){
setTimeout(function(){
alert('Hello'+this.name);
},0);
}
}
vargreeting=newdelayedGreeting('Peter')
greeting.greet();//alerts'Helloundefined'
www.EBooksWorld.ir
Whenexecutingtheprecedingscript,wewon'tgettheexpectedHelloPeteralert,butanincompletestringhighlightingapeskygreetingtoMr.Undefined!Basically,thisconstructionscrewsthelexicalscopingofthiswhenevaluatingthefunctioninsidethetimeoutcall.Portingthisscripttoarrowfunctionswilldothetrickthough:
functiondelayedGreeting(name):void{
this.name=name;
this.greet=function(){
setTimeout(()=>alert('Hello'+this.name),0);
}
}
Evenifwebreakdownthestatementcontainedinthearrowfunctionintoseverallinesofcodewrappedbycurlybraces,thelexicalscopingofthiswillkeeppointingtothepropercontextoutsidethesetTimeoutcall,allowingamoreelegantandcleansyntax.
www.EBooksWorld.ir
Classes,interfaces,andclassinheritanceNowthatwehaveoverviewedthemostrelevantbitsandpiecesofTypeScript,it'stimetoseehoweverythingfallsintoplacetobuildTypeScriptclasses.TheseclassesarethebuildingblocksofTypeScriptandAngular2applications.
AlthoughthenounclasswasareservedwordinJavaScript,thelanguageitselfneverhadanactualimplementationfortraditionalPOO-orientedclassesasotherlanguagessuchasJavaorC#did.JavaScriptdevelopersusedtomimicthiskindoffunctionality,leveragingthefunctionobjectasaconstructortype,whichwouldbelateroninstancedwiththenewoperator.Othercommonpracticessuchasextendingourfunctionobjectswereimplementedbyapplyingprototypalinheritanceorbyusingcomposition.
Nowwehaveanactualclassfunctionality,whichisflexibleandpowerfulenoughtoimplementthefunctionalityourapplicationsrequire.Wealreadyhadthechancetotapintoclassesinthepreviouschapter.Let'slookattheminmoredetailnow.
www.EBooksWorld.ir
Anatomyofaclass–constructors,properties,methods,getters,andsettersThefollowingpieceofcodeillustrateshowaclasscouldbe.Pleasenotethattheclasspropertymemberscomefirstandthenweincludeaconstructorandseveralmethodsandpropertyaccessors.Noneofthemfeaturesthereservedwordfunctionandallthemembersandmethodsareproperlyannotatedwithatypeexceptconstructor:
classCar{
privatedistanceRun:number=0;
color:string;
constructor(publicisHybrid:boolean,color:string='red'){
this.color=color;
}
getGasConsumption():string{
returnthis.isHybrid?'Verylow':'Toohigh!';
}
drive(distance:number):void{
this.distanceRun+=distance;
}
statichonk():string{
return'HOOONK!';
}
getdistance():number{
returnthis.distanceRun;
}
}
ThisclasslayoutwillprobablyremindusofthecomponentclasswebuiltbackinChapter1,CreatingOurVeryFirstComponentinAngular2.Basically,theclassstatementwrapsseveralelementsthatwecanbreakdowninto:
Members:AnyinstanceoftheCarclasswillfeaturetwoproperties:colortypedasstring,anddistanceRuntypedasanumberandonlyaccessiblefromwithintheclassitself.Ifweinstancethisclass,distanceRunoranyothermemberormethodmarkedasprivatewon'tbepubliclyexposedaspartoftheobjectAPI.Constructor:Theconstructorfunctionisexecutedrightawaywhenaninstanceoftheclassiscreated.Usually,wewanttoinitializetheclassmembershere,withthedataprovidedintheconstructorsignature.Wecanalsoleveragetheconstructorsignatureitselftodeclareclassmembers,aswedidwiththeisHybridproperty.Todoso,wejustneedtoprefixtheconstructorparameterwithanaccessmodifiersuchasprivateorpublic.Sameaswesawwhenanalyzingfunctionsintheprevioussections,wecandefinerest,optional,ordefaultparametersasdepictedinthepreviousexamplewiththecolorargument,whichfallbacksto"red"whennotexplicitlydefined.Methods:Amethodisaspecialkindofmemberwhichrepresentsafunctionand
www.EBooksWorld.ir
therefore,canreturn,ornot,atypedvalue.Basically,itisafunctionthatbecomespartoftheobjectAPI.Methodscanbeprivateaswell.Inthatcase,theyarebasicallyusedashelperfunctionswithintheinternalscopeoftheclasstoachievethefunctionalitiesrequiredbyotherclassmembers.Staticmembers:Membersmarkedasstaticareassociatedwiththeclassandnotwiththeobjectinstancesofthatclass.Thismeansthatwecanconsumestaticmembersdirectly,withouthavingtoinstantiateanobjectfirst.Infact,staticmembersarenotaccessiblefromtheobjectinstancesandthus,theycannotaccessotherclassmembersusingthis.Thesemembersareusuallyincludedintheclassdefinitionashelperorfactorymethodsinordertoprovideagenericfunctionalitynotrelatedtoanyspecificobjectinstance.Propertyaccessors:InES5,wecoulddefinecustomsetters/gettersinaveryverbosewaywithObject.defineProperty.Now,thingshavebecomequitesimpler.Inordertocreatepropertyaccessors(usuallypointingtointernalprivatefieldsasintheexampleprovided),wejustneedtoprefixatypedmethodnamedasthepropertywewanttoexposewithset(inordertomakeitwritable)andget(inordertomakeitreadable).
Asapersonalexercise,whydon'tyoucopytheprecedingpieceofcodeattheplaygroundpage(http://www.typescriptlang.org/Playground)andexecuteit?WecanevenseeaninstanceobjectoftheCarclassinactionbyappendingthissnippetrightaftertheclassdefinitionandrunningthecodeandinspectingtheoutputinthebrowser'sdevelopertoolsconsole:
varmyCar=newCar(false);
console.log(myCar.color);//'red'
//PublicaccessorreturnsdistanceRun:
console.log(myCar.distance);//0
myCar.drive(15);
console.log(myCar.distance);//15(0+15)
myCar.drive(21);
console.log(myCar.distance);//36(15+21)
//What'smycarbonfootprintaccordingtomycartype?
myCar.getGasConsumption();//'Toohigh!'
Car.honk();//'HOOONK!'noobjectinstancerequired
Wecanevenperformanadditionaltestandappendthefollowingillegalstatementstoourcode,whereweattempttoaccesstheprivatepropertydistanceRunorevenapplyavaluethroughthedistancemember,whichdoesnothaveagetter.
console.log(myCar.distanceRun);
myCar.distance=100;
Rightafterinsertingthesecodestatementsintheplaygroundtextfield,aredunderlinewillremarkthatweareattemptingtodosomethingthatisnotcorrect.Nevertheless,wecancarryonandtranspileandrunthecode,sinceES5willhonorthesepractices.Allinall,ifweattempttorunthetsccompileronthisfile,theruntimewillexitwiththefollowingerror
www.EBooksWorld.ir
trace:
example_26.ts(21,7):errorTS1056:Accessorsareonlyavailablewhen
targetingECMAScript5andhigher.
example_26.ts(29,13):errorTS2341:Property'distanceRun'isprivateand
onlyaccessiblewithinclass'Car'.
www.EBooksWorld.ir
InterfacesinTypeScriptAsapplicationsscaleandmoreclassesandconstructsarecreated,weneedtofindwaystoensureconsistencyandrulescomplianceinourcode.Oneofthebestwaystoaddresstheconsistencyandtypevalidationissueistocreateinterfaces.
Inanutshell,aninterfaceisacodeblueprintdefiningacertainfieldsschemaandanytypes(eitherclasses,functionsignatures)implementingtheseinterfacesaremeanttocomplywiththisschema.Thisbecomesquiteusefulwhenwewanttoenforcestricttypingonclassesgeneratedbyfactories,whenwedefinefunctionsignaturestoensurethatacertaintypedpropertyisfoundinthepayload,orothersituations.
Let'sgetdowntobusiness!Here,wedefinetheVehicleinterface.Vehicleisnotaclassbutacontractualschemathatanyclasswhichimplementsitmustcomplywith:
interfaceVehicle{
make:string;
}
AnyclassimplementingtheVehicleinterfacemustfeatureamembernamedmake,whichmustbetypedasastringaccordingtothisexample.Otherwise,theTypeScriptcompilerwillcomplain:
classCarimplementsVehicle{
//Compilerwillraiseawarningif'make'isnotdefined
make:string;
}
Interfacesarethereforeextremelyusefultodefinetheminimumsetofmembersanytypemustfulfill,becominganinvaluablemethodforensuringconsistencythroughoutourcodebase.
Note
Itisimportanttonotethatinterfacesarenotusedjusttodefineminimumclassschemas,butanytypeoutthere.Thisway,wecanharnessthepowerofinterfacesforenforcingtheexistenceofcertainfieldsandmethodsinclassesandpropertiesinobjectsusedlateronasfunctionparameters,functiontypes,typescontainedinspecificarrays,andevenvariables.Aninterfacemaycontainoptionalmembersaswellandevenmemberstypedasotherinterfaces.
Let'screateanexample.Todoso,wewillprefixallourinterfacetypeswithanI(uppercase).Thisway,itwillbeeasiertofinditstypewhenreferencingthemwithourIDEcodeautocompletionfunctionality.
First,wedefineanIExceptioninterfacethatmodelsatypewithamandatorymessagepropertymemberandanoptionalIDnumbermember:
interfaceIException{
www.EBooksWorld.ir
message:string;
id?:number;
}
Wecandefineinterfacesforarrayelementsaswell.Todoso,wemustdefineaninterfacewithasolemember,definingindexaseitheranumberorstring(fordictionarycollections)andthenthetypewewantthatarraytocontain.Inthiscase,wewanttocreateaninterfaceforarrayscontainingIExceptiontypes.ThisisatypecomprisingastringmessagepropertyandanoptionalIDnumbermember,aswesaidinthepreviousexample:
interfaceIExceptionArrayItem{
[index:number]:IException;
}
Nowwedefinetheblueprintforourfutureclass,withatypedarrayandamethodwithitsreturningtypedefinedaswell:
interfaceIErrorHandler{
exceptions:IExceptionArrayItem[];
logException(message:string,id?:number):void;
}
Wecanalsodefineinterfacesforstandaloneobjecttypes.Thisisquiteusefulwhenitcomestodefiningatemplatedconstructorormethodsignatures,whichwewillseelaterinthisexample:
interfaceIExceptionHandlerSettings{
logAllExceptions:boolean;
}
Lastbutnotleast,inthefollowingclasswewillimplementalltheseinterfacetypes:
classErrorHandlerimplementsIErrorHandler{
exceptions:IExceptionArrayItem[];
logAllExceptions:boolean;
constructor(settings:IExceptionHandlerSettings){
this.logAllExceptions=settings.logAllExceptions;
}
logException(message:string,id?:number):void{
this.exceptions.push({message,id});
}
}
Basically,wearedefininganerrorhandlerclassherethatwillmanageaninternalarrayofexceptionsandexposeamethodtolognewexceptionsbysavingthemintotheaforementionedarray.ThesetwoelementsaredefinedbytheIErrorHandlerinterfaceandaremandatory.TheclassconstructorexpectstheparametersdefinedbytheIExceptionHandlerSettingsinterfaceandusesthemtopopulateexceptionmemberwithitemstypedasIException.InstancingtheErrorHandlerclasswithoutthelogAllExceptions
www.EBooksWorld.ir
parameterinthepayloadwilltriggeranerror.
Let'swrapupthissectionaboutinterfacesbyhighlightingthatclassescanimplementmorethanoneinterface.
www.EBooksWorld.ir
ExtendingclasseswithclassinheritanceJustlikeaclasscanbedefinedbyaninterface,itcanalsoextendthemembersandfunctionalityofotherclassasiftheywereitsown.Wecanmakeaclassinheritfromanotherbyappendingthekeywordextendstotheclassname,includingthenameoftheclasswewanttoinherititsmembersfrom:
classSedanextendsCar{
model:string;
constructor(make:string,model:string){
super(make);
this.model=model;
}
}
Here,weextendfromaparentclass,Car,whichalreadyexposedamakemember.Wecanpopulatethemembersalreadydefinedbytheparentclassandevenexecutetheirownconstructorbyexecutingthesuper()method,whichpointstotheparentconstructor.Wecanalsooverridemethodsfromtheparentclassbyappendingamethodwiththesamename.Nevertheless,wewillstillbeabletoexecutetheoriginalparent'sclassmethodsasitwillbestillaccessiblefromthesuperobject.Comingbacktotheinterface,theycanalsoinheritdefinitionfromotherinterfaces.Simplyput,aninterfacecaninheritfromanotherinterface.
Note
Asawordofcaution,ES6andTypeScriptdonotprovidesupportformultipleinheritance.So,youmaywanttousecompositionormiddlemanclassesinstead,incaseyouwanttoborrowfunctionalitiesfromdifferentsources.
www.EBooksWorld.ir
DecoratorsinTypeScriptDecoratorsareaverycoolfunctionality,originallyproposedbyGoogleinAtScript(asupersetofTypeScriptthatfinallygotmergedintoTypeScriptbackinearly2015)andalsoapartofthecurrentstandardpropositionforECMAScript7.Inanutshell,decoratorsareawaytoaddmetadatatoclassdeclarationsforusebydependencyinjectionorcompilationdirectives(http://blogs.msdn.com/b/somasegar/archive/2015/03/05/typescript-lt-3-angular.aspx).Bycreatingdecorators,wearedefiningspecialannotationsthatmayhaveanimpactonthewayourclasses,methods,orfunctionsbehaveorjustsimplyalteringthedatawedefineinfieldsorparameters.Inthatsense,decoratorsareapowerfulwaytoaugmentourtype'snativefunctionalitieswithoutcreatingsubclassesorinheritingfromothertypes.
Thisis,byfar,oneofthemostinterestingfeaturesofTypeScript.Infact,itisextensivelyusedinAngular2whendesigningdirectivesandcomponentsormanagingdependencyinjection,aswewillseefromChapter4,EnhancingourComponentswithPipesandDirectives,onwards.
Note
Decoratorscanbeeasilyrecognizedbythe@prefixtotheirname,andtheyareusuallylocatedasstandalonestatementsabovetheelementtheydecorate,includingamethodpayloadornot.
Wecandefineuptofourdifferenttypesofdecorators,dependingonwhatelementeachtypeismeanttodecorate:
ClassdecoratorsPropertydecoratorsMethoddecoratorsParameterdecorators
Let'stakealookateachofthem!
www.EBooksWorld.ir
ClassdecoratorsClassdecoratorsallowustoaugmentaclassorperformoperationsoveranyofitsmembers,andthedecoratorstatementisexecutedbeforetheclassgetsinstanced.
Creatingaclassdecoratorjustrequiresdefiningaplainfunction,whosesignatureisapointertotheconstructorbelongingtotheclasswewanttodecorate,typedasFunction(oranyothertypethatinheritsfromFunction).TheformaldeclarationdefinesaClassDecoratorasfollows:
declaretypeClassDecorator=<TFunctionextendsFunction>(Target:TFunction)=>
TFunction|void;
Yes,itisreallydifficulttograspwhatthisgibberishmeans,right?Let'sputeverythingincontextthroughasimpleexample,likethis:
functionGreeter(target:Function):void{
target.prototype.greet=function():void{
console.log('Hello!');
}
}
@Greeter
classGreeting{
constructor(){
//Implementationgoeshere...
}
}
varmyGreeting=newGreeting();
myGreeting.greet();//consolewilloutput'Hello!'
Aswecansee,wehavegainedagreet()methodthatwasnotoriginallydefinedintheGreetingclassjustbyproperlydecoratingitwiththeGreeterdecorator.
Extendingtheclassdecoratorfunctionsignature
Sometimes,wemightneedtocustomizethewayourdecoratoroperatesuponinstancingit.Noworries!Wecandesignourdecoratorswithcustomsignaturesandthenhavethemreturningafunctionwiththesamesignaturewedefinewhendesigningclassdecoratorswithnoparameters.Asaruleofthumb,decoratorstakingparametersjustrequireafunctionwhosesignaturematchestheparameterswewanttoconfigure.Suchafunctionmustreturnanotherfunction,whosesignaturematchesthatofthedecoratorwewanttodefine.
Thefollowingpieceofcodeillustratesthesamefunctionalityasthepreviousexample,butallowsdeveloperstocustomizethegreetingmessage:
functionGreeter(greeting:string){
returnfunction(target:Function){
target.prototype.greet=function():void{
www.EBooksWorld.ir
console.log(greeting);
}
}
}
@Greeter('Howdy!')
classGreeting{
constructor(){
//Implementationgoeshere...
}
}
varmyGreeting=newGreeting();
myGreeting.greet();//consolewilloutput'Howdy!'
www.EBooksWorld.ir
PropertydecoratorsPropertydecoratorsaremeanttobeappliedonclassfieldsandcanbeeasilydefinedbycreatingaPropertyDecoratorfunction,whosesignaturetakestwoparameters:
target:Thisistheprototypeofclasswewanttodecoratekey:Thisisthenameofthepropertywewanttodecorate
Possibleusecasesforthisspecifictypeofdecoratormayencompassfromloggingthevalueassignedtoclassfieldswheninstancingobjectsofsuchaclassandevenreactingtodatachangesonsuchfields.Let'sseeanactualexamplethatencompassesboththesebehaviors:
functionLogChanges(target:Object,key:string){
varpropertyValue:string=this[key];
if(deletethis[key]){
Object.defineProperty(target,key,{
get:function(){
returnpropertyValue;
},
set:function(newValue){
propertyValue=newValue;
console.log(`${key}isnow${propertyValue}`);
}
});
}
}
classFruit{
@LogChanges
name:string;
}
varfruit=newFruit();
fruit.name='banana'; //consoleoutputs'nameisnowbanana'
fruit.name='plantain';//consoleoutputs'nameisnowplantain'
Thesamelogicforparametrizedclassdecoratorsapplieshere,althoughthesignatureofthereturnedfunctionisslightlydifferentinordertomatchthatoftheparameter-lessdecoratordeclarationwealreadysaw.
Thefollowingexampledepictshowwecanlogchangesonagivenclasspropertyandtriggeracustomfunctionwhenthisoccurs:
functionLogChanges(callbackObject:any):Function{
returnfunction(target:Object,key:string):void{
varpropertyValue:string=this[key];
if(deletethis[key]){
Object.defineProperty(target,key,{
get:function(){
returnpropertyValue;
},
www.EBooksWorld.ir
set:function(newValue){
propertyValue=newValue;
callbackObject.onchange.call(this,
propertyValue);
}
});
}
}
}
classFruit{
@LogChanges({
onchange:function(newValue:string):void{
console.log(`Thefruitis${newValue}now`);
}
})
name:string;
}
varfruit=newFruit();
fruit.name='banana';//console:'Thefruitisbanananow'
fruit.name='plantain';//console:'Thefruitisplantainnow'
www.EBooksWorld.ir
MethoddecoratorsThesespecialdecoratorscandetect,log,andinterveneinhowmethodsareexecuted.Todoso,wejustneedtodefineaMethodDecoratorfunctionwhosepayloadtakesthefollowingparameters:
target:Thisistypedasanobjectandrepresentsthemethodbeingdecorated.key:Thisisastringthatgivestheactualnameofthemethodbeingdecorated.value:Thisisapropertydescriptorofthegivenmethod.Infact,it'sahashobjectcontaining,amongotherthings,apropertynamedvaluewithareferencetothemethoditself.
Let'sseehowwecanleveragetheMethodDecoratorfunctioninanactualexample.Supposewewanttobuildamultipurpose,signature-agnosticloggerthatwillkeeptrackoftheoutputreturnedbyeachmethodinourclassuponexecution,includingsomeadditionaldatasuchasthetimestampwhenthemethodwasexecuted:
functionLogOutput(target:Function,key:string,descriptor:any){
varoriginalMethod=descriptor.value;
varnewMethod=function(...args:any[]):any{
varresult:any=originalMethod.apply(this,args);
if(!this.loggedOutput){
this.loggedOutput=newArray<any>();
}
this.loggedOutput.push({
method:key,
parameters:args,
output:result,
timestamp:newDate()
});
returnresult;
};
descriptor.value=newMethod;
}
Aswementionedearlier,thedescriptorparametercontainsareferencetothemethodwewanttodecorate.Withthisinmind,nothingpreventsusfromreplacingsuchamethodbyourown.Wecantakeadvantageofthisnewlycreatedmethodtoexecutetheformerbypassingalongtoitthesameparameters.
Note
Rememberthatdecoratorfunctionsarescopedwithintheclassrepresentedinthetargetparameter,sowecantakeadvantageofthatforaugmentingtheclasswithourowncustommembers.Becarefulwhendoingthis,sincethismightoverridethealreadyexistingmembersthough.Forthesakeofthisexample,wewon'tapplyanyduediligenceoverthis,buthandlethiswithcareinyourcodeinthefuture.
www.EBooksWorld.ir
Aswecansee,ourcodeisprettysimpleandstraightforward.WearebasicallystoringareferencetotheoriginalmethodintheoriginalMethodvariable,borrowingitfromdescriptor.value.Then,wearebuildinganewmethodwitharestargumentthatensureswecoveranypossiblemethodsignatureoutthere.Internally,thisnewmethodexecutesthepreviousoneandstorestheresultalongsidesomeotherdata(suchasthenameofthemethodexecuted,theparametersused,andthetimeanddateitoccurredon)inanewlycreatedclassarrayfield,whichiscreatedonthespotincaseitdidn'texistyet.Wethenreturnthecomputedresult,ifany,aswewouldexpectfromtheoriginalmethodwehavejustoverridden.
Let'sputourShinydecoratortothetest!Let'screateaclasswithamethodperformingmethodcomputationsandloggingandseewhathappens:
classCalculator{
@LogOutput
double(num:number):number{
returnnum*2;
}
}
varcalc=newCalculator();
calc.double(11);
console.log(calc.loggedOutput);//Check[Object]arrayinconsole
Here,ourShinyCalculatorclassexposesamethodthatwilldoubleupanynumberwepickasaninput.But,isitproperlyloggingtheoperationsmade?Let'sinspectinthevalueofcalc.loggedOutputinthebrowserdevtoolsconsoleandseewhathappens.
Wecanevenruntheextramileandaddadifferentmethodwithacompletelydifferenttypeandsignatureandseewhethereverythingkeepsworkingfine.AddthistothebodyoftheCalculatorclass:
@LogOutput
doNothing(input:any):any{
returninput;
}
Now,executethefollowingintheDevToolsconsolecommandline:
calc.doNothing(['LearningAngular2',2016]);
Ifyoucheckthevalueofcalc.loggedOutput,youwillseethenewobjectalongwiththepreviouscomputationwemadeshowingupattheconsole.
Thefollowinglineinourexamplemusthavecaughtyourattention:
this.loggedOutput=newArray<any>();
Weneedtoproperlytypethememberweaimtocreateinourclass.Todoso,andsincewecannotannotatethetypethenormalwayhere,weleveragethegenericconstructorofthe
www.EBooksWorld.ir
Arrayobjectpassingtheanytypealongbetweentheanglebrackets.
ThischapterwillnotcoverGenericssincetheyarekindofoutofthescopeofanintroductorybooklikethis,butweencourageyoutorefertotheofficialsite(http://www.typescriptlang.org/Handbook#generics)anddelvedeeperintothetopic.Forthetimebeing,youjustneedtoknowthatgenericsallowustoenforceacertaintypewhenexecutingcertaincustommethodsorusingspecificclassessuchasarrays.Wewillseestrictlytypedarraysornewlycreatedemptyobjectsthataremeanttoenforceacertaininterfacethankstothefunctionalitiesprovidedbytheuseofgenerics.Again,pleaserefertotheofficialsiteoftheTypeScriptlanguageforfurtherinformation.
www.EBooksWorld.ir
ParameterdecoratorsOurlastroundofdecoratorswillcovertheParameterDecoratorfunction,whichtapsintoparameterslocatedinfunctionsignatures.Thissortofdecoratorisnotintendedtoaltertheparameterinformationorthefunctionbehavior,buttolookintotheparametervalueandthenperformoperationselsewhere,suchas,forargument'ssake,loggingorreplicatingdata.TheParameterDecoratorfunctiontakesthefollowingparameters:
target:Thisistheobjectprototypewherethefunction,whoseparametersaredecorated,usuallybelongstoaclasskey:ThisisthenameofthefunctionwhosesignaturecontainsthedecoratedparameterparameterIndex:Thisistheindexintheparametersarraywherethisdecoratorhasbeenapplied
Thefollowingexampleshowsaworkingexampleofaparameterdecorator:
functionLog(target:Function,key:string,parameterIndex:number){
varfunctionLogged=key||target.prototype.constructor.name;
console.log(`
Theparameterinposition${parameterIndex}at${functionLogged}has
beendecorated
`);
}
classGreeter{
greeting:string;
constructor(@Logphrase:string){
this.greeting=phrase;
}
}
//Theconsolewilloutputrightaftertheclassaboveisdefined:
//'Theparameterinposition0atGreeterhasbeendecorated'
YouhaveprobablynoticedtheweirdassignationofthefunctionLoggedvariable.Thisisbecausethevalueofthetargetparameterwillvarydependingonthefunctionwhoseparametersarebeingdecorated.Therefore,itisdifferentifwedecorateaconstructorparameteroramethodparameter.Theformerwillreturnareferencetotheclassprototypeandthelatterwilljustreturntheconstructorfunction.Thesameappliesforthekeyparameter,whichwillbeundefinedwhendecoratingtheconstructorparameters.
Aswementionedinthebeginningofthissection,parameterdecoratorsarenotmeanttomodifythevalueoftheparametersdecoratedoralterthebehaviorofthemethodsorconstructorswheretheseparameterslive.Theirpurposeisusuallytologorpreparethecontainerobjectforimplementingadditionallayersofabstractionorfunctionalitythroughhigher-leveldecorators,suchasmethodorclassdecorators.Usualcasescenariosforthisencompassloggingcomponentbehaviorormanagingdependencyinjection,aswewillseeinChapter4,EnhancingOurComponentswithPipesandDirectives.
www.EBooksWorld.ir
OrganizingourapplicationswithmodulesAsourapplicationsscaleandgrowinsize,therewillbeatimewhenwewillneedtobetterorganizeourcodetomakeitsustainableandmorereusable.Modulesaretheresponseforthisneed,solet'stakealookathowtheyworkandhowwecanimplementtheminourapplication.Modulescanbeeitherinternalorexternal.Inthisbook,wewillmostlyfocusonexternalmodules,butitisagoodideatooverviewthetwotypesnow.
www.EBooksWorld.ir
InternalmodulesInanutshell,internalmodulesaresingletonwrapperscontainingarangeofclasses,functions,objects,orvariablesthatarescopedinternally,awayfromtheglobalorouterscope.Wecanpubliclyexposethecontentsofamodulebyprefixingthekeywordexporttotheelementwewanttobeaccessiblefromtheoutside,likethis:
moduleGreetings{
exportclassGreeting{
constructor(publicname:string){
console.log(`Hello${name}`);
}
}
exportclassXmasGreeting{
constructor(publicname:string){
console.log(`MerryXmas${name}`);
}
}
}
OurGreetingsmodulecontainstwoclassesthatwillbeaccessiblefromoutsidethemodulebyimportingthemoduleandaccessingtheclasswewanttousebyitsname:
importXmasGreeting=Greetings.XmasGreeting;
varxmasGreeting=newXmasGreeting('Joe');
//consoleoutputs'MerryXmasJoe'
Afterlookingattheprecedingcode,wecanconcludethatinternalmodulesareagoodwaytogroupandencapsulateelementsinanamespacecontext.Wecanevensplitourmodulesintoseveralfiles,aslongasthemoduledeclarationkeepsthesamenameacrossthesefiles.Inordertodoso,wewillwanttoreferencethedifferentfileswherewehavescatteredobjectsbelongingtothismodulewithreferencetags:
///<referencepath="greetings/XmasGreeting.ts"/>
ThemajordrawbackofinternalmodulesthoughisthatinordertoputthemtoworkoutsidethedomainofourIDE,weneedtohavealloftheminthesamefileorapplicationscope.WecanincludeallthegeneratedJavaScriptfilesasscriptinsertsinourwebpages,leveragetaskrunnerssuchasGruntorGulpforthat,orevenusethe--outFileflagintheTypeScriptcompilertohaveallthe.tsfilesfoundinyourworkspacecompiledintoasinglebundleusingabootstrapfilewithreferencetagstoalltheothermodulesasthestartingpointforourcompilation:
tsc--outFileapp.jsmodule.ts
ThiswillcompilealltheTypeScriptfilesfollowingthetrailofdependentfilesreferencedwithreferencetags.Ifweforgettoreferenceanyfilethisway,itwillnotbeincludedinthefinalbuildfile,soanotheroptionistoenlistallthefilescontainingstandalonemodulesinthe
www.EBooksWorld.ir
compilingcommandorjustadda.txtfilecontainingacomprehensivelistofthemodulestobundle.Alternatively,wecanjustuseexternalmodulesinstead.
www.EBooksWorld.ir
ExternalmodulesExternalmodulesareprettymuchthesolutionweneedwhenitcomestobuildingapplicationsdesignedtogrow.Basically,eachexternalmoduleworksatafilelevel,whereeachfileisthemoduleitselfandthemodulenamewillmatchthefilenamewithoutthe.jsextension.WedonotusethemodulekeywordanymoreandeachmembermarkedwiththeexportprefixwillbecomepartoftheexternalmoduleAPI.TheinternalmoduledepictedinthepreviousexamplewouldturnintothisonceconvenientlysavedintheGreetings.tsfile:
exportclassGreeting{
constructor(publicname:string){
console.log(`Hello${name}`);
}
}
exportclassXmasGreeting{
constructor(publicname:string){
console.log(`MerryXmas${name}`);
}
}
Importingthismoduleandusingitsexportedclasseswouldrequirethefollowingcode:
importgreetings=require('Greetings');
varXmasGreetings=greetings.XmasGreetings();
varxmasGreetings=newXmasGreetings('Pete');
//consoleoutputs'MerryXmasPete'
Obviously,therequirefunctionisnotsupportedbytraditionalJavaScript,soweneedtoinstructthecompilerabouthowwewantthatfunctionalitytobeimplementedinourtargetJavaScriptfiles.Fortunately,theTypeScriptcompilerincludesthe--moduleparameterinitsAPI,sowecanconfigurethedependencyloaderofchoiceforourproject:commonjsfornode-styleimports,amdforRequireJS-basedimports,umdforaloaderimplementingtheUniversalModuleDefinitionspecification,orsystemforSystemJS-basedimports.WewillfocusontheSystemJSmoduleloaderthroughoutthisbook:
tsc--outFileapp.js--modulecommonjs
Theresultingfilewillbeproperlyshimmed,somodulescanloaddependenciesacrossfilesusingourmoduleloaderofchoice.
www.EBooksWorld.ir
TheroadaheadWereachednowtheendofthischapter.However,webarelyscratchedthesurfaceofTypeScript.Itshugepotentialgoeswaybeyondthescopeofthisshallowintroductionandthereisdefinitelymoreofthiswhichisworthbeingexploredandanalyzedindetail.Unfortunately,thatisoutofthescopeofthisbookandsuchinformationisnotevenrequiredtolearnandunderstandAngular2,althoughthisknowledgewillobviouslyexpandtheboundariesofyourcodepractice.Wewouldsuggestyoutocheckthefollowingaspectsofthelanguagespecificationasastartingpoint:
GenericsMixinsIntersectiontypesUniontypesTuplesCreatingdeclarationtypefiles
IfyouwanttodelvedeeperintoTypeScript,IwouldrecommendyoureadTypeScriptEssentialsbyChristopherNance,LearningTypeScriptbyRemoH.Jansen,orthemoreadvancedMasteringTypeScriptbyNathanRozentals,allbyPacktPublishing.
www.EBooksWorld.ir
SummaryThiswasdefinitelyalongread,butthisintroductiontoTypeScriptwasabsolutelynecessaryinordertounderstandthelogicbehindmanyofthemostbrilliantpartsofAngular2.Itgaveusthechancetonotonlyintroducethelanguagesyntax,butalsoexplaintherationalebehinditssuccessasthesyntaxofchoiceforbuildingtheAngular2framework.Werevieweditstypearchitectureandhowwecancreateadvancedbusinesslogicdesigningfunctionswithawiderangeofalternativesforparametrizedsignaturesandevendiscoveredhowtobypasstheissuesrelatedwithscopebyusingthepowerfulnewarrowfunctions.Probablythemostrelevantpartofthischapterencompassedtheoverviewofclasses,methods,properties,andaccessorsandhowwecanhandleinheritanceandbetterapplicationdesignthroughinterfaces.Modulesanddecoratorsweresomeothermajorfeaturesexploredinthischapterand,aswewillseeverysoon,havingasoundknowledgeofthesemechanismsisparamounttounderstandhowdependencyinjectionworksinAngular2.
Withallthisknowledgeatourdisposal,wecannowresumeourinvestigationofAngular2andconfrontwithconfidencetherelevantpartsofcomponentcreationsuchasstyleencapsulation,outputformatting,andsoon.Chapter3,ImplementingPropertiesandEventsinOurComponents,willexposeustoadvancedtemplatecreationtechniques,data-bindingtechniques,directives,andpipes.AllthesefeatureswillallowustoputinpracticeallthisnewlygainedknowledgeofTypeScript.
www.EBooksWorld.ir
Chapter3.ImplementingPropertiesandEventsinOurComponentsSofar,wehavehadtheopportunitytotakeabird'seyeoverviewofwhatacomponentisinthenewAngularecosystem,whatisitsrole,howitbehaves,andwhattoolsarerequiredtostartbuildingourowncomponentstorepresentwidgetsandpiecesoffunctionality.Inaddition,TypeScriptturnsouttobetheperfectcompanionforthisendeavor,soweseemtohaveeverythingthatweneedtofurtherexplorethepossibilitiesthatAngular2bringstothegamewithregardstocreatinginteractivecomponentsthatexposepropertiesandemitevents.
Inthischapter,wewill:
DiscoverallthesyntacticpossibilitiesatourdisposaltobindcontentinourtemplatesCreatepublicAPIsforourcomponentssothatwecanbenefitfromtheirpropertiesandeventhandlersSeehowtoimplementdatabindinginAngular2ReducethecomplexityofCSSmanagementwithviewencapsulation
www.EBooksWorld.ir
AbettertemplatesyntaxInChapter1,CreatingOurVeryFirstComponentinAngular2,wesawhowtoembedHTMLtemplatesinourcomponents,butwedidn'tevenscratchthesurfaceoftemplatedevelopmentforAngular2.Aswewillseelaterinthisbook,templateimplementationistightlycoupledwiththeprinciplesofShadowDOMdesignandbringsforthalotofsyntacticsugartoeasethetaskofbindingpropertiesandeventsinourviewsinadeclarativefashion.
Inanutshell,AngularcomponentsmayexposeapublicAPIthatallowsthemtocommunicatewithothercomponentsorcontainers.ThisAPImayencompassinputproperties,whichweusetofeedthecomponentwithdata.Italsomayexposeoutputpropertieswecanbindeventlistenersto,therebygettingpromptinformationaboutchangesinthestateofthecomponent.
Let'stakealookatthewayAngularsolvestheproblemofinjectingdatainandoutofourcomponentsthroughquickandeasyexamples.Pleasefocusonthephilosophybehindtheseproperties.Wewillhaveachancetoseetheminactionlateronwhenwefollowupwithourpomodoroproject.
www.EBooksWorld.ir
DatabindingswithinputpropertiesLet'srevisitthepomodorofunctionalitythatwealreadysawinChapter1,CreatingOurVeryFirstComponentinAngular2,andlet'simaginethatwewantourcomponenttohaveaconfigurableattributesothatwecanincreaseordecreasethecountdowntime:
<pomodoro-timer[seconds]="25"></pomodoro-timer>
Pleasenotetheattributewrappedbetweenbrackets.ThisinformsAngularthatthisisaninputproperty.Theclassthatmodelsthepomodoro-timercomponentwillcontainasetterfunctionforthesecondsproperty,whichwillreacttochangesinthatvaluebyupdatingitsowncountdownduration.Wecaninjectadatavariableoranactualhardcodedvalue,inwhichcasewewillhavetowrapitaroundsinglequoteswithinthedoublequotesshouldsuchavaluebeatextstring.
Sometimes,wewillseethissyntaxwhileinjectingdataintoourcomponent'scustomproperties,whileatothertimes,wewillusethisverybracketsyntaxtomakenativeHTMLattributesreactivetocomponentfields,likethis:
<h1[hidden]="hideMe">
Thistextwillnotbevisibleif'hideMe'istrue
</h1>
www.EBooksWorld.ir
SomeextrasyntacticsugarwhenbindingexpressionsTheAngularteamhasmadeavailablesomeshortcutsforperformingcommontransformationsinourcomponentdirectivesandDOMelements,suchastweakingattributesandclassnamesorapplyingstyles.Here,wehavesomeexamplesofgreattime-saverswhendeclarativelydefiningbindingsinourproperties:
<div[attr.hidden]="isHidden">...</div>
<input[class.is-valid]="isValid">
<div[style.width.px]="myWidth">...</div>
Inthefirstcase,divwillenablethehiddenattributeshouldtheisHiddenexpressionevaluatetotrue.BesidesBooleanvalues,wecanbindanyotherdatatype,suchasastringvalue.Inthesecondcase,theis-validclassnamewillbeinjectedintheclassattributeiftheisValidexpressionevaluatestotrue.Inourthirdexample,divwillfeatureastyleattributethatshowsoffawidthpropertymeanttobesetwiththevalueofthemyWidthexpressionsinpixels.YoucanfindmoreexamplesofthissyntacticsugarintheAngular2cheatsheet(https://angular.io/cheatsheet)availableattheofficialAngularsite.
www.EBooksWorld.ir
EventbindingwithoutputpropertiesLet'simaginewewantourpomodorotimercomponenttonotifyuswhenthecountdownisfinishedsothatwecanperformsomeotheractionsoutsidetherealmofthecomponent.Wecanachievesuchfunctionalitywithanoutputpropertylikethis:
<pomodoro-timer(countdownComplete)="onCountownCompleted()"></pomodoro-timer>
Notetheattributewrappedbetweenbraces.ThisinformsAngularthatsuchanattributeis,infact,anoutputpropertythatwilltriggertheeventhandlerwebindtoit.Inthiscase,wewillwanttocreateanonCountownCompletedeventhandleronthecontainerobjectthatwrapsthiscomponent.
Tip
Bytheway,thecamelcaseisnotacoincidence.ItisanamingconventionappliedtoalloutputandinputpropertynamesinAngular2.
Wewillfindoutputpropertiesmappedtointeractioneventsthatwealreadyknow,suchasclick,mouseover,mouseout,focus,andmore.
<button(click)="doSomething()">Clickme</button>
www.EBooksWorld.ir
InputandoutputpropertiesinactionThebestwaytograsptheconceptsdetailedintheearliersectionsisbypractice.Let'sstripdownthepomodorotimerexamplethatwesawinChapter1,CreatingOurVeryFirstComponentinAngular2,anddiscussasimplerexample.Openthepomodoro-timer.tsfileandreplaceitscontentswiththefollowingcomponentclass:
import{Component}from'@angular/core';
import{bootstrap}from'@angular/platform-browser-dynamic';
@Component({
selector:'countdown',
template:'<h1>Timeleft:{{seconds}}</h1>'
})
classCountdownComponent{
seconds:number=25;
intervalId:number;
constructor(){
this.intervalId=setInterval(()=>this.tick(),1000);
}
privatetick():void{
if(--this.seconds<1){
clearInterval(this.intervalId);
}
}
}
Great!Wehavejustdefinedasimplebuthighlyeffectivecountdowntimercomponentthatwillcountdownto0from25seconds.(Doyouseethesecondsfieldupthere?TypeScriptsupportstheinitializationofmembersupondeclaringthem).AsimplesetIntervalloopexecutesacustomprivatefunctionnamedtick()thatdecreasesthevalueofsecondsuntilitreacheszero,inwhichcasewejustcleartheinterval.
PleasenoticethatwedidnotincludeanycalltothebootstrapfunctiontoinstantiatethiscomponentinaHTMLdocument,eventhoughweareeffectivelyimportingthefunction.Thisisactuallyprettycommon,sinceweonlycallthebootstrapfunctiontoinstantiatethetopparentcomponent(alsoknownasrootcomponent)andalltheothercomponents,definedaschildcomponentsoftheformer,willbeautomaticallyinstantiatedincascade.
However,nowwejustneedtoembedthiscomponentsomewhere,solet'screateanothercomponentwithnofunctionalityotherthanactingasaHTMLwrapperhostforthepreviouscomponent.CreatethisnewcomponentrightaftertheCountdownComponentclassinitssamefile:
@Component({
selector:'pomodoro-timer',
directives:[CountdownComponent],
template:'<countdown></countdown>'
www.EBooksWorld.ir
})
classPomodoroTimerComponent{}
bootstrap(PomodoroTimerComponent);
Aswecanseethere,weintroducedanewpropertynameddirectivesinthecomponentinitializationanddeclaredCountdownComponentthere.ComponentsinAngular2arebasicallydirectiveswithaviewtemplate.Wecanalsofinddirectiveswithnoview,whichbasicallyaddnewfunctionalitiestotheirhostelement;ortheyjustactascustomelementswithoutaUIthatwrapsotherelements.Alternatively,theysimplyprovidefurtherfunctionalitiestoothercomponentsbymeansoftheirAPI.
Wewillexploredirectivesindetailinthenextchapterandalsoalongthebook.Fornow,let'sjustpointoutthatwhenwedefineacomponentcontainingothercomponents,aswehavejustdone,wewillhavetoexplicitlydeclaretheimmediatechildrencomponents'classesinthedirectivesarrayparameter.YoumustbewonderingwhyhavewecreatedthishostorparentPomodoroTimerComponentcomponentwithnoimplementation.Soon,wewillfleshitoutwithsomemorefeatures,butfornowlet'suseitasaproofofconceptforhowtoinitiateacomponenttree.
Settingupcustomvaluesdeclaratively
Youwillprobablyagreeonthefactthathavingthefunctionalityofsettingupcustomcountdowntimerswouldbenice,right?Inputpropertiesturnouttobeanexcellentwaytoachievethis.Inordertoleveragethisfunctionality,wewillhavetotweaktheimportstatementatthetopofthefile:
import{Component,Input}from'@angular/core';
Let'supdateourpomodorotimeraccordingly:
@Component({
selector:'countdown',
template:'<h1>Timeleft:{{seconds}}</h1>'
})
classCountdownComponent{
@Input()seconds:number;
intervalId:number;
//Restofimplementationremainsthesame...
}
Youmighthavealreadynoticedthatwearenolongerinitializingthesecondsfield,anditisnowdecoratedwithapropertydecorator(aswesawinChapter2,IntroducingTypeScript).WehavejuststartedtodefinetheAPIofourcomponent.
Note
Propertynamingiscasesensitive,andtheconventionenforcedbyAngular2istoapply
www.EBooksWorld.ir
camelcasetocomponentinputand,aswewillseeshortly,outputpropertiesalike.
Next,wejustneedtoaddthedesiredpropertyinourcontainercomponent'stemplate:
@Component({
selector:'pomodoro-timer',
directives:[CountdownComponent],
template:`<divclass="containertext-center">
<imgsrc="assets/img/pomodoro.png"/>
<countdown[seconds]="25"></countdown>
</div>`
})
PleasenotethatwehavenotupdatedthePomodorotimerComponentatall.WeonlyupdateditsCountdownComponentchildrencomponent.However,itsbrandnewAPIbecomesavailabletoanycomponentthateventuallyincludesitinitsowntemplateasachildcomponent,sowecansetupitspropertiesdeclarativelyrightfromthetemplate,orevenbindavalueimperativelyfromapropertylocatedatthePomodorotimerComponentcontrollerclassifwewish.
Tip
Whenflaggingaclasspropertywith@Input(),wecanconfigurethenamewewantthispropertytohaveuponinstantiatingthecomponentintheHTML.Todoso,wejustneedtointroduceournameofchoiceinthedecoratorsignature,likethis@Input('name_of_the_property').Inanyevent,thispracticeisdiscouragedsinceexposingpropertynamesinthecomponentAPIdistinctfromtheonesdefinedinitscontrollerclasscanonlyleadtoconfusion.
Communicatingbetweencomponentsthroughcustomevents
Nowthatourchildcomponentisbeingconfiguredbyitsparentcomponent,howcanweachievecommunicationfromthechildtotheparent?Thisiswherecustomeventscometotherescue!Inordertocreatepropereventbindings,wejustneedtoconfigureanoutputpropertyinourcomponentandattachaneventhandlerfunctiontoit.
Inordertotriggercustomevents,wewillneedtobringEventEmittertotheparty,alongwiththe@Outputdecorator,whosefunctionalityisexactlytheoppositetowhatwelearnedregardingthe@Inputdecorator:
import{Component,Input,Output,EventEmitter}from'@angular/core';
EventEmitteristhebuilt-ineventbusofAngular2.Inanutshell,theEventEmitterclassprovidessupportforemittingObservabledataandsubscribingObserverconsumerstodatachanges.Itssimpleinterface,whichbasicallyencompasstwomethods,emit()andsubscribe(),canthereforebeusedtotriggercustomeventsandlistentoeventsaswell,bothsynchronouslyorasynchronously.WewilldiscussObservablesinmoreindetailinChapter6,AsynchronousDataServiceswithAngular2.Forthetimebeing,wecangetawaywiththe
www.EBooksWorld.ir
ideathatwewillbeusingtheEventEmitterAPItospawneventsthatlistenermethodsinthecomponentshostingourevent-emittingcomponentcanobserveandattacheventhandlersto.Theseeventsacquirevisibilityoutsidethescopeofthecomponentthroughanyofitspropertiesannotatedwiththe@Input()decorator.
Thefollowingcodeshowsanactualimplementationthatfollowsupfromthepreviousexample:
@Component({
selector:'countdown',
template:'<h1>Timeleft:{{seconds}}</h1>'
})
classCountdownComponent{
@Input()seconds:number;
intervalId:number;
@Output()complete:EventEmitter<any>=newEventEmitter();
constructor(){
this.intervalId=setInterval(()=>this.tick(),1000);
}
privatetick():void{
if(--this.seconds<1){
clearTimeout(this.intervalId);
//Aneventisemitteduponfinishingthecountdown
this.complete.emit(null);
}
}
}
AnewpropertynamedcompleteisconvenientlyannotatedwiththeEventEmittertypeandinitializedonthespot.Lateronwewillaccessitsemitmethodtospawnacustomeventassoonasthecountdownends.Theemit()methodneedsonemandatoryparameterofanytype,sowecansendadatavaluetotheeventsubscribers(ornullifnotrequired).
Now,wejustneedtosetupourhostcomponentsothatitwilllistentothiscompleteeventoroutputpropertyandsubscribeaneventhandlertoit:
@Component({
selector:'pomodoro-timer',
directives:[CountdownComponent],
template:`<divclass="containertext-center">
<imgsrc="assets/img/pomodoro.png"/>
<countdown[seconds]="25"
(complete)="onCountdownCompleted()">
</countdown>
</div>`
})
classPomodoroTimerComponent{
onCountdownCompleted():void{
alert('Timeup!');
}
}
www.EBooksWorld.ir
Tip
WhycompleteandnotonComplete?
Angular2providessupportforanalternativesyntaxnamedcanonicalformforbothinputandoutputproperties.Inthecaseofinputproperties,apropertyrepresentedas[seconds]couldberepresentedasbind-seconds,withouttheneedforbrackets.Withregardstooutputproperties,thesecanberepresentedason-completeinsteadof(complete).Thatiswhyweneverprefixoutputpropertynameswithanonprefix,sincethatwouldconcuronoutputpropertiessuchason-on-completeincaseweeventuallydecidetofavorthecanonicalsyntaxforminourprojects.
www.EBooksWorld.ir
EmittingdatathroughcustomeventsNowthatweknowhowtoemitcustomeventsfromourcomponentAPI,whydon'twetakeastepfurtherandsenddatasignalsbeyondthescopeofthecomponent?Wealreadydiscussedthattheemit()eventoftheEventEmitter<T>classacceptsinitssignatureanygivendataofthetyperepresentedbytheTannotation.Let'sextendourexampletonotifytheprogressofthecountdown.Whywouldweeverwanttodothis?Basically,ourcomponentdisplaysonscreenavisualcountdown,butwemightwanttowatchthecountdownprogressprogrammaticallyinordertotakeactiononcethecountdownisfinishedorreachesacertainpoint.
Let'supdateourtimercomponentwithanotheroutputpropertythatmatchestheoriginalandemitsacustomeventoneachiterationofthesecondsproperty,asfollows:
classCountdownComponent{
@Input()seconds:number;
intervalId:number;
@Output()complete:EventEmitter<any>=newEventEmitter();
@Output()progress:EventEmitter<number>=newEventEmitter();
constructor(){
this.intervalId=setInterval(()=>this.tick(),1000);
}
privatetick():void{
if(--this.seconds<1){
clearTimeout(this.intervalId);
this.complete.emit(null);
}
this.progress.emit(this.seconds);
}
}
Now,let'srebuildourhostcomponent'stemplatetoreflecttheactualprogressofthecountdown.Wealreadydosobydisplayingthecountdown,butthatisafeaturehandledinternallybytheCountdownComponent.Now,wewillkeeptrackofthecountdownoutsidethiscomponent:
@Component({
selector:'pomodoro-timer',
directives:[CountdownComponent],
template:`<divclass="containertext-center">
<imgsrc="assets/img/pomodoro.png"/>
<countdown[seconds]="25"
(progress)="timeout=$event"
(complete)="onCountdownCompleted()">
</countdown>
<p*ngIf="timeout<10">
Beware!Only<strong>{{timeout}}seconds</strong>left.
</p>
</div>`
})
classPomodoroTimerComponent{
www.EBooksWorld.ir
timeout:number;
onCountdownCompleted():void{
alert('Timeup!');
}
}
Wetookadvantageofthisroundofchangestoformalizethetimeoutvalueasapropertyofthehostcomponent.Thisallowsustobindnewvaluestothatpropertyinourcustomeventhandlers,aswedidintheprecedingexample.Ratherthanbindinganeventhandlermethodtothe(progress)handler,werefertothe$eventreservedvariable.Itisapointertothepayloadoftheprogressoutputpropertythatreflectsthevaluewepasstotheemit()functionwhenexecutingthis.progress.emit(this.seconds).Inshort,$eventisthevalueassumedbythis.secondsinsideCountdownComponent.Byassigningsuchvaluetothetimeoutclasspropertywithinthetemplate,wearealsoupdatingthebindingexpressedintheparagraphwejustinsertedinthetemplate.Thisparagraphwillonlybecomevisiblewhentimeoutislowerthan10.
<countdown[seconds]="25"
on-progress="timeout=$event"
on-complete="onCountdownCompleted()">
</countdown>
www.EBooksWorld.ir
LocalreferencesintemplatesWehavepreviouslyseenhowwecanbinddatatoourtemplatesusingdatainterpolationwiththedoublecurlybracessyntax.Besidesthis,wewillquiteoftenspotnamedidentifiersprefixedbyahashsymbol(#)intheelementsbelongingtoourcomponentsorevenregularHTMLcontrols.Thesereferenceidentifiers,namelylocalnames,areusedtorefertothecomponentsflaggedwiththeminourtemplateviewsandthenaccessthemprogrammatically.TheycanalsobeusedbycomponentstorefertootherelementsinthevirtualDOMandaccessitsproperties.
Intheprevioussection,wesawhowwecouldsubscribetothecountdownprogressthroughtheprogressevent.But,whatifwecouldinspectthecomponentindepth,oratleastitspublicpropertiesandmethods,andreadthevaluethatthesecondspropertytakesoneachtickintervalwithouthavingtolistentotheprogressevent?Well,settingalocalreferenceonthecomponentitselfwillopenthedoortoitspublicfaçade.
Let'sflagtheinstanceofourCountdownComponentinthePomodoroTimerComponenttemplatewithalocalreferencenamed#counter.Fromthatverymoment,wewillbeabletodirectlyaccessthecomponent'spublicproperties,suchasseconds,andevenbinditinotherlocationsofthetemplate.Thisway,wedonotevenneedtorelyontheprogresseventemitterorthetimeoutclassfield,andwecanevenmanipulatethevalueofsuchproperties.Thisisshowninthefollowingcode:
@Component({
selector:'pomodoro-timer',
directives:[CountdownComponent],
encapsulation:ViewEncapsulation.None,
template:`<divclass="containertext-center">
<imgsrc="assets/img/pomodoro.png"/>
<countdown[seconds]="25"
(complete)="onCountdownCompleted()"
#counter>
</countdown>
<p>
<buttonclass="btnbtn-default"(click)="counter.seconds=
25">Resetcountdownto25seconds
</button>
</p>
<p*ngIf="counter.seconds<10">
Beware!Only
<strong>{{counter.seconds}}seconds</strong>
left.
</p>
</div>`
})
classPomodoroTimerComponent{
//timeout:number;/*Nolongerrequired*/
onCountdownCompleted():void{
alert('Timeup!');
}
www.EBooksWorld.ir
}
www.EBooksWorld.ir
AlternativesyntaxforinputandoutputpropertiesBesidesthe@Input()and@Output()decorators,thereisanalternativesyntaxwherewecandefineinputandoutputpropertiesinourcomponentsbymeansofthe@Componentdecorator.Itsmetadataimplementationprovidessupportforbothfeaturesthroughtheinputsandoutputspropertynames,respectively.
TheCountdownComponentAPIcouldthereforebeimplementedlikethis:
@Component({
selector:'countdown',
template:'<h1>Timeleft:{{seconds}}</h1>',
inputs:['seconds'],
outputs:['complete','progress']
})
classCountdownComponent{
seconds:number;
intervalId:number;
complete:EventEmitter<any>=newEventEmitter();
progress:EventEmitter<number>=newEventEmitter();
//Etc...
}
Allinall,thissyntaxisdiscouragedandhasbeenincludedhereforreferencepurposesonly.Inthefirstplace,weduplicatecodebydefiningthenamesofourAPIendpointsintwoplacesatthesametime,increasingtheriskoferrorswhenrefactoringcode.Itisalsoacommonconventiontokeepthedecoratorimplementationsasleanaspossibleinordertoimprovereadability.
Tip
Istronglysuggestthatyousticktothe@Inputand@Outputdecorators.
www.EBooksWorld.ir
ConfiguringourtemplatefromourcomponentclassTheComponentmetadataalsosupportsseveralsettingsthatcontributetoeasytemplatemanagementandconfiguration.Ontheotherhand,Angular2takesadvantageoftheCSSencapsulationfunctionalitiesofWebComponents.
www.EBooksWorld.ir
InternalandexternaltemplatesAsourapplicationsgrowinsizeandcomplexity,chancesarethatourtemplateswillgrowaswell,hostingothercomponentsandbiggerchunksofHTMLcode.Embeddingallthiscodeinourcomponentclassdefinitionswillbecomeacumbersomeandunpleasanttaskandquitepronetoerrorsbytheway.Inordertopreventthisfromhappening,wecanleveragethetemplateUrlproperty,pointingtoastandaloneHTMLfilethatcontainsourcomponentHTMLmarkup.
Backtoourpreviousexample,wecanrefactorthe@ComponentdecoratorofourPomodoroTimerComponentclasstopointtoanexternalhtmlfilecontainingourtemplate.Createanewfilenamedpomodoro-timer.htmlinthesameworkspacewhereourpomodoro-timer.tsfilelivesandpopulateitwiththesameHTMLweconfiguredinourPomodoroTimerComponentclass:
<divclass="containertext-center">
<imgsrc="assets/img/pomodoro.png"alt="Pomodoro"/>
<countdown
[seconds]="25"
(complete)="onCountdownCompleted()"
#counter>
</countdown>
<p>
<buttonclass="btnbtn-default"(click)="counter.seconds=25">
Resetcountdownto25seconds
</button>
</p>
<p*ngIf="counter.seconds<10">
Beware!Only
<strong>{{counter.seconds}}seconds</strong>
left.
</p>
</div>
Now,wecanpolishour@ComponentdecoratortopointtothatfileinsteadofdefiningtheHTMLinsidethedecoratormetadata:
@Component({
selector:'pomodoro-timer',
directives:[CountdownComponent],
templateUrl:'./pomodoro-tasks.html'
})
classPomodoroTimerComponent{
//Classfollowsbelow...
}
Note
ExternaltemplatesfollowacertainconventioninAngular2,enforcedbythemostpopularAngular2codingstyleguidesoutthere,whichistosharethesamefilenamethanthecomponenttheybelongto,includinganyfilenameprefixorsuffixwemightappendtothe
www.EBooksWorld.ir
componentfilename.WewillseethiswhenexploringcomponentnamingconventionsinChapter5,BuildinganApplicationwithAngular2Components.Thisway,itiseasiertorecognize,orevensearchwithyourIDEsearchbuilt-infuzzyfindertool,whatHTMLfileisinfactthetemplateofaspecificcomponent.
Whatisthethresholdforcreatingstandalonetemplatesratherthankeepingthetemplatemarkupinsidethecomponent?Itdependsonthecomplexityandsizeofthetemplate.Commonsensewillbeyourbestadvisorinthiscase.
www.EBooksWorld.ir
EncapsulatingCSSstylingInordertobetterencapsulateourcodeandmakeitmorereusable,wecandefineCSSstylingwithinourcomponents.Theseinternalstylesheetsareagoodwaytomakeourcomponentsmoreshareableandmaintainable.TherearethreedifferentwaysofdefiningCSSstylingforourcomponents.
Thestylesproperty
WecandefinestylesforourHTMLelementsandclassnamesthroughthestylepropertyinthecomponentdecorator,likethis:
@Component({
selector:'my-component',
styles:[`
p{
text-align:center;
}
table{
margin:auto;
}`]
})
ThispropertywilltakeanarrayofstringscontainingCSSruleseachandapplythemtothetemplatemarkupbyembeddingthoserulesattheheadofthedocumentassoonaswebootstrapourapplication.Wecaneitherinlinethestylingrulesinasingleline,ortakeadvantageofES2015templatestringstoindentthecodeandmakeitmorereadableasdepictedintheexampleabove.
ThestyleUrlsproperty
Justlikestyles,styleUrlswillacceptanarrayofstrings,althougheachonewillrepresentalinktoanexternalstylesheetthough.Thispropertycanbeusedalongsidethestylespropertyaswell,definingdifferentsetsofruleswhererequired:
@Component({
selector:'my-component',
styleUrls:['path/to/my-stylesheet.css'],
styles:[`
p{
text-align:center;
}
table{
margin:auto;
}`]
})
Inlinestylesheets
Wecanalsoattachthestylingrulestothetemplateitself,nomatterwhetherit'saninlinetemplateoratemplateservedthroughthetemplateUrlparameter:
www.EBooksWorld.ir
@Component({
selector:'app',
template:`
<style>p{color:red;}</style>
<p>Iamaredparagraph</p>`
})
www.EBooksWorld.ir
ManagingviewencapsulationAlltheprecedingsections(styles,styleUrls,andinlinestylesheets)willbegovernedbytheusualrulesofCSSspecificity(https://developer.mozilla.org/en/docs/Web/CSS/Specificity).CSSmanagementandspecificitybecomesabreezeonbrowsersthatsupportShadowDOM,thankstoscopedstyling.CSSstylesapplytotheelementscontainedinthecomponentbutdonotspreadbeyonditsboundaries.
Ontopofthat,Angularwillembedthesestylesheetsattheheadofthedocument,sotheymightaffectotherelementsofourapplication.Inordertopreventthisfromhappening,wecansetupdifferentlevelsofviewencapsulation.
Inanutshell,encapsulationisthewayAngularneedstomanageCSSscopingwithinthecomponentforbothShadowDOM-compliantbrowsersandthosethatdonotsupportit.Forallthis,weleveragetheViewEncapsulationenum,whichcantakeanyofthesevalues:
Emulated:Thisisthedefaultoption,anditbasicallyentailsanemulationofnativescopinginShadowDOMthroughsandboxingtheCSSrulesunderaspecificselectorthatpointstoourcomponent.Thisoptionispreferredtoensurethatourcomponentstyleswillnotbeaffectedbyotherexistinglibrariesonoursite.Native:UsethenativeShadowDOMencapsulationmechanismoftherenderer,anditonlyworksonbrowsersthatsupportShadowDOM.None:Templateorstyleencapsulationisnotprovided.Thestyleswillbeinjectedasisintothedocument'sheader.
Let'scheckoutanactualexample.First,importtheViewEncapsulationenumintothescript,andthencreateanencapsulationpropertywiththeEmulatedvalue.Then,let'screateastyleruleforourcountdowntextsoany<h1>(!)tagisrenderedindarkred:
import{
Component,
EventEmitter,
Input,
Output,
ViewEncapsulation
}from'@angular/core';
import{bootstrap}from'@angular/platform-browser-dynamic';
@Component({
selector:'countdown',
template:'<h1>Timeleft:{{seconds}}</h1>',
styles:['h1{color:#900}'],
encapsulation:ViewEncapsulation.Emulated
})
classCountdownComponent{
//Etc...
}
Now,clickonthebrowser'sdevtoolsinspectorandcheckthegeneratedHTMLtodiscover
www.EBooksWorld.ir
howAngular2injectedtheCSSinsidethepage<HEAD>block.ThejustinjectedstylesheethasbeensandboxedtoensurethattheglobalCSSrulewedefinedatthecomponentsetupinaverynon-specificwayforall<h1>elementsonlyappliestomatchingelementsscopedbytheCountdownComponentcomponentexclusively.
WerecommendthatyoutryoutdifferentvaluesandseehowtheCSScodeisinjectedintothedocument.Youwillimmediatelynoticethedifferentgradesofisolationthateachvariationprovides.
www.EBooksWorld.ir
SummaryThischapterguidedusthroughtheoptionsavailableinAngular2forcreatingpowerfulAPIsforourcomponents,sowecanprovidehighlevelsofinteroperabilitybetweencomponents,configuringitspropertiesbyassigningeitherstaticvaluesormanagedbindings.Wealsosawhowacomponentcanactasahostcomponentforanotherchildcomponent,instantiatingtheformer'scustomelementinitsowntemplate,settingthegroundupforlargercomponenttreesinourapplications.Outputparametersgivethelayerofinteractivityweneedbyturningourcomponentsintoeventemitterssotheycanproperlycommunicateinanagnosticfashionwithanyparentcomponentthatmighteventuallyhostthem.Templatereferencespavedthewaytocreatereferencesinourcustomelementsthatwecanuseasaccessorstotheirpropertiesandmethodsfromwithinthetemplateinadeclarativefashion.Wealsodiscussedhowwecouldisolatethecomponent'sHTMLtemplateinanexternalfileinordertoeaseitsfuturemaintainabilityandhowtodothesamewithanystylesheetwewantedtobindtothecomponent,incasewedonotwanttobundlethecomponentstylesinline.Anoverviewofthebuilt-infeaturesforhandlingviewencapsulationinAngular2gaveussomeadditionalinsightsonhowwecanbenefitfromShadowDOM'sCSSscopingonapercomponentbasisandhowwecanpolyfillitwhennotsupported.
WestillhavemuchmoretolearnregardingtemplatemanagementinAngular2,mostlywithregardstothetwoconceptsthatyouwilluseextensivelyalongyourjourneywithAngular.IamreferringtoDirectivesandPipes,whichwewillcoverextensivelyinChapter4,EnhancingourComponentswithPipesandDirectives.
www.EBooksWorld.ir
Chapter4.EnhancingOurComponentswithPipesandDirectivesInthepreviouschapters,webuiltseveralcomponentsthatrendereddataonscreenwiththehelpofinputandoutputproperties.Wewillleveragetheknowledgeinthischaptertotakeourcomponentstothenextlevelwiththeuseofdirectivesandpipes.Inanutshell,whilepipesgiveustheopportunitytodigestandtransformtheinformationwebindinourtemplates,directivesallowustoconductmoreambitiousfunctionalitieswherewecanaccessthehostelementpropertiesandalsobindourveryowncustomeventlistenersanddatabindings.
Inthischapter,wewill:
Haveacomprehensiveoverviewofthebuilt-indirectivesofAngular2DiscoverhowwecanrefineourdataoutputwithpipesSeehowwecandesignandbuildourowncustompipesanddirectivesLeveragebuilt-inobjectsformanipulatingourtemplatesPutalltheprecedingtopicsandmanymoreintopracticebyfollowinguponourpomodoroprojecttobuildafullyinteractiveto-doitemstable
www.EBooksWorld.ir
DirectivesinAngular2Angular2definesdirectivesascomponentswithoutviews.Infact,acomponentisadirectivewithanassociatedtemplateview.ThisdistinctionisusedbecausedirectivesareaprominentpartoftheAngular2coreandeach(plaindirectivesandcomponentdirectives)needstheothertoexist.DirectivescanbasicallyaffectthewayHTMLelementsorcustomelementsbehaveanddisplaytheircontent.
www.EBooksWorld.ir
CoredirectivesLet'stakeacloserlookattheframework'scoredirectives,andthenyouwilllearnhowtobuildyourowndirectiveslateroninthischapter.
NgIf
Astheofficialdocumentationstates,theNgIfdirectiveremovesorrecreatesaportionoftheDOMtreebasedonanexpression.IftheexpressionassignedtotheNgIfdirectiveevaluatestofalse,thentheelementisremovedfromtheDOM.Otherwise,acloneoftheelementisreinsertedintotheDOM.Wecouldenhanceourcountdowntimerbyleveragingthisdirective,likethis:
<pomodoro-timer[seconds]="timeout"></pomodoro-timer>
<p*ngIf="timeout===0">Timeup!</p>
Whenourpomodorotimerreaches0,theparagraphthatdisplaystheTimeup!textwillberenderedonthescreen.Youhaveprobablynoticedthatasteriskthatprependsthedirective.ThisisbecauseAngularembedstheHTMLcontrolmarkedwiththeNgIfdirective(andallitsHTMLsubtrees,ifany)ina<template>tag,whichwillbeusedlaterontorenderthecontentonthescreen.CoveringhowAngulartreatstemplatesisdefinitelyoutofthescopeofthisbook,butlet'sjustpointoutthatthisissyntacticsugarprovidedbyAngulartoactasashortcuttothatother,moreverbosesyntaxbasedontemplatetags.
PerhapsyouarewonderingwhatdifferencedoesitmaketorendersomechunkofHTMLonscreenwith*ngIf="conditional"ratherthanwith[hidden]="conditional".TheformerwillcloneandinjectpiecesoftemplatedHTMLsnippetsinthemarkup,removingitfromtheDOMwhentheconditionevaluatestofalse,whilethelatterdoesnotinjectorremoveanymarkupfromtheDOM.ItsimplysetsthevisibilityofthealreadyexistingchunkofHTMLannotatedwiththatDOMattribute.
NgFor
TheNgFordirectiveallowsustoiteratethroughacollection(oranyotheriterableobject)andbindeachofitsitemstoatemplateofourchoice,wherewecandefineconvenientplaceholderstointerpolatetheitemdata.Eachinstantiatedtemplateisscopedtotheoutercontext,wheretheloopdirectiveisplaced,sowecanaccessotherbindings.Let'simaginewehaveacomponentnamedStaff:itfeaturesafieldnamedemployees,whichrepresentsanarrayofEmployeeobjects.Wecanenlistthoseemployeesandjobtitlesinthisway:
<ul>
<li*ngFor="letemployeeofemployees;leti=index;letlast=last">
Employee#{{i}}:-{{employee.name}},{{employee.position}}
<span*ngIf="last"><br/>Endoflist</span>
</li>
</ul>
Aswecanseeintheexampleprovided,weturneachitemfetchedfromtheiterableobjecton
www.EBooksWorld.ir
eachloopintoalocalreferencesothatwecaneasilybindthisiteminourtemplate.Here,wealsousethesyntaxsugarthatweusedintheprevioussection,andAngulargivesustheopportunitytoassignindextoascopedvariablethatwillbesettothecurrentloopiterationinthetemplatecontext.Wecanalsoassignlasttoascopedvariablethatwillinformwhethertheitemisthelastoneintheiteration.
Thisdirectiveobserveschangesintheunderlyingiterableobjectandwilladd,remove,orsorttherenderedtemplatesasitemsareadded,removed,orreorderedinthecollection.
NgStyle
Asyouprobablyhaveguessedalready,thisdirectiveallowsustobindCSSstylesbyevaluatingacustomobjectorexpression.WecanbindanobjectwhosekeysandvaluesmapCSSproperties,orjustdefinespecificpropertiesandbinddatatothem:
<p[ngStyle]="{'color':myColor,'font-weight':myFontWeight}">Iamredand
bold</p>
IfourcomponentdefinesthemyColorandmyFontWeightpropertieswiththeredandboldvalues,respectively,thecolorandweightofthetextwillchangeaccordingly.Thedirectivewillalwaysreflectthechangesmadewithinthecomponent,andwecanalsopassanobjectinsteadofbindingdataonaperpropertybasis:
<p[ngStyle]="myCssConfig">Iamredandbold</p>
NgClass
SimilartoNgStyle,NgClassallowsustodefineandtoggleclassnamesprogrammaticallyinaDOMelementusingaconvenientdeclarativesyntax.Thissyntaxhasitsownintricacies,however.Let'sseeeachoneofthethreecasescenariosavailableforthisexample:
<p[ng-class]="{{myClassNames}}">HelloAngular!</p>
Forinstance,wecanuseastringtypesothatifmyClassNamescontainsastringwithoneorseveralclassesdelimitedbyaspace,allofthemwillbeboundtotheparagraph.
Wecanuseanarrayaswellsothateachelementwillbeadded.
Lastbutnotleast,wecanuseanobjectinwhicheachkeycorrespondstoaCSSclassnamereferredtobyaBooleanvalue.Eachkeynamemarkedastruewillbecomeanactiveclass.Otherwise,itwillberemoved.Thisisusuallythepreferredwayofhandlingclassnames.
NgSwitch,NgSwitchWhen,andNgSwitchDefault
TheNgSwitchdirectiveisusedtoswitchtemplateswithinaspecificsetdependingontheconditionrequiredfordisplayingeachone.Theimplementationfollowsseveralsteps,thereforethreedifferentdirectivesareexplainedinthissection.
www.EBooksWorld.ir
NgSwitchwillevaluateagivenexpressionandthentoggleanddisplaythosechildelementsmarkedwithanngSwitchWhenattributedirective,whosevaluematchesthevaluethrownbytheexpressiondefinedintheparentngSwitchelement.AspecialmentionisrequiredaboutthechildrenelementmarkedwiththengSwitchDefaultdirectiveattribute.ThisattributequalifiesthetemplatethatwillbedisplayedwhennoothervaluedefinedbyitsngSwitchWhensiblingsmatchestheparentconditionalexpression.
We'llseeallofthisinanexample:
<div[ngSwitch]="weatherForecastDay">
<templatengSwitchWhen="today">{{weatherToday}}</template>
<templatengSwitchWhen="tomorrow">
{{weatherTomorrow}}</template>
<templatengSwitchDefault>
Pickadaytoseetheweatherforecast
</template>
</div>
Theparent[ngSwitch]parameterevaluatestheweatherForecastDaycontextvariable,andeachnestedngSwitchWhendirectivewillbetestedagainstit.Wecanuseexpressionsinstead,butwewanttowrapngSwitchWheninbracketssothatAngularcanproperlyevaluateitscontentascontextvariablesinsteadoftakingitasatextstring.
Note
Atthetimeofclosingthewritingofthisbook,theAngularcoreteamisdiscussingtheconvenienceofrenamingngSwitchWhentongSwitchCaseinordertokeepconsistentwithJavaScriptandTypeScriptswitch/casekeywords,andalsowithotheri18ndirectivessuchasNgPluralandNgPluralCase.ItisquitelikelythatthisbreakingchangewillmakeittoAngular2finalsopleaserefertotheonlinedocumentationtodoublecheckthefinalsyntaxforNgSwitchtemplatecases.
CoveragefortheNgPluralandNgPluralCasesitsoutsideofthescopeofthisbook,butbasicallyprovideaconvenientwaytorenderorremovetemplatesDOMblocksthatmatchaswitchexpression,eitherstrictlynumericorjustastring,inasimilarfashiontohowtheNgSwitchandNgSwitchWhendirectivesdo.
www.EBooksWorld.ir
ManipulatingtemplatebindingswithPipesSo,wesawhowwecanusedirectivestorendercontentdependingonthedatathatourcomponentclassesmanage,butthereisanotherpowerfulfeaturethatwewillbeusingthoroughlyinourdailypracticewithAngular.WearetalkingaboutPipes.
Pipesallowustofilterandfunneltheoutcomeofourexpressionsonaviewleveltotransformorjustbetterdisplaythedatawearebinding.Theirsyntaxisprettysimple,basicallyconsistingofthepipenamefollowingtheexpressionthatwewanttotransform,separatedbyapipesymbol(hencethename):
@Component({
selector:"greeting",
template:"HELLO{{name|uppercase}}"
})
classGreetingComponent{
name:string;
}
Intheprecedingexample,wearedisplayinganuppercasegreetingonthescreen.Sincewedonotknowwhetherthenamewillbeinuppercaseornot,weensureaconsistentoutputbytransformingthevalueofthenamewheneveritisnotanuppercaseversionattheviewlevel.Pipesarechainable,andAngularhasawiderangeofpipetypesalreadybakedin.Aswewillseefurtherinthischapter,wecanalsobuildourownpipestofine-graindataoutputincaseswherethebuilt-inpipesaresimplynotenough.
www.EBooksWorld.ir
Theuppercase/lowercasepipeThenameuppercase/lowercasepipesaysitall.Asintheexampleprovidedpreviously,thispipesetsthestringoutputinuppercaseorlowercase.Insertthefollowingcodeanywhereinyourviewandcheckouttheoutputforyourself:
<p>{{'helloworld'|uppercase}}</p>
<!--outputs'HELLOWORLD'-->
<p>{{'wEIrDhElLo'|lowercase}}</p>
<!--outputis'weirdhello'-->
www.EBooksWorld.ir
Thenumber,percent,andcurrencypipesNumericdatacancomeinawiderangeofflavors,andthispipeisespeciallyconvenientwhenitcomestobetterformattingandlocalizingtheoutput.ThesepipesusetheInternationalizationAPI,andthereforetheyarereliableinChromeandOperabrowsersonly.
Thenumberpipe
Thenumberpipewillhelpusdefinethegroupingandsizingofnumbersusingtheactivelocaleinourbrowser.Itsformatisasfollows:
expression|number[:digitInfo]
Here,expressionisanumberanddigitInfohasthefollowingformat:
{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
Eachbindingwouldcorrespondtothefollowing:
minIntegerDigits:Theminimumnumberofintegerdigitstouse.Itdefaultsto1.minFractionDigits:Theminimumnumberofdigitsafterthefraction.Itdefaultsto0.maxFractionDigits:Themaximumnumberofdigitsafterthefraction.Itdefaultsto3.
Note
Keepinmindthattheacceptablerangeforeachofthesenumbersandotherdetailswilldependonyournativeinternationalizationimplementation.
Thepercentpipe
Thepercentpipeformatsanumberaslocalpercent.Otherthanthis,itinheritsfromtheNumberpipesothatwecanfurtherformattheoutputtoprovideabetterintegeranddecimalsizingandgrouping.Itssyntaxisasfollows:
expression|percent[:digitInfo]
Thecurrencypipe
Formatsanumberasalocalcurrency,providingsupportforselectingthecurrencycodesuchasUSDfortheUSdollarorEURfortheeuroandsettinguphowwewantthecurrencyinfotobedisplayed.Itssyntaxisasfollows:
expression|currency[:currencyCode[:symbolDisplay[:digitInfo]]]
Intheprecedingstatement,currencyCodeisobviouslytheISO4217currencycode,whilesymbolDisplayisaBooleanthatindicateswhethertousethecurrencysymbol(forexample,$)orthecurrencycode(for,exampleUSD)intheoutput.Thedefaultforthisvalueisfalse.Similartothenumberandpercentpipes,wecanformattheoutputtoprovideabetterintegeranddecimalsizingandgroupingthroughthedigitInfovalue:
www.EBooksWorld.ir
<p>{{11256.569|currency:"GBP":true:'4.1-2'}}</p>
<!--outputis'£11,256.57'-->
www.EBooksWorld.ir
TheslicepipeThepurposeofthispipeisequivalenttotheroleplayedbyArray.prototype.slice()andString.prototype.slice()whenitcomestosubtractingasubset(slice)ofacollectionlist,array,orstring,respectively.Itssyntaxisprettystraightforwardandfollowsthesameconventionsasthoseoftheaforementionedslice()methods:
expression|slice:start[:end]
Basically,weconfigureastartingindexwherewewillbeginslicingeithertheitemsarrayorthestringonanoptionalendindex,whichwillfallbacktothelastindexontheinputwhenomitted.
Note
Bothstartandendargumentscantakepositiveandnegativevalues,astheJavaScriptslice()methodsdo.RefertotheJavaScriptAPIdocumentationforafullrundownonalltheavailablescenarios.
Lastbutnotleast,pleasenotethatwhenoperatingonacollection,thereturnedlistisalwaysacopy—evenwhenallelementsarebeingreturned.
www.EBooksWorld.ir
ThedatepipeYoumusthavealreadyguessedthattheDatepipeformatsadatevalueasastringbasedontherequestedformat.Thetimezoneoftheformattedoutputwillbethelocalsystemtimezoneoftheenduser'smachine.Itssyntaxisprettysimple:
expression|date[:format]
Theexpressioninputmustbeadateobjectoranumber(millisecondssincetheUTCepoch).Theformatargumentishighlycustomizableandacceptsawiderangeofvariationsbasedondate-timesymbols.Forourconvenience,somealiaseshavebeenmadeavailableasshortcutstothemostcommondateformats:
'medium':Thisisequivalentto'yMMMdjms'(forexample,Sep3,2010,12:05:08PMforen-US)'short':Thisisequivalentto'yMdjm'(forexample,9/3/2010,12:05PMforen-US)'fullDate':Thisisequivalentto'yMMMMEEEEd'(forexample,Friday,September3,2010foren-US)'longDate':Thisisequivalentto'yMMMMd'(forexample,September3,2010)'mediumDate':Thisisequivalentto'yMMMd'(forexample,Sep3,2010foren-US)'shortDate':Thisisequivalentto'yMd'(forexample,9/3/2010foren-US)'mediumTime':Thisisequivalentto'jms'(forexample,12:05:08PMforen-US)'shortTime':Thisisequivalentto'jm'(forexample,12:05PMforen-US)
www.EBooksWorld.ir
TheJSONpipeJSONisprobablythemoststraightforwardpipeinitsdefinition;itbasicallytakesanobjectasaninputandoutputsitinJSONformat:
{{{name:'Eve',age:43}|json}}
Hereistheoutput:
{"name":"Eve","age":43}
www.EBooksWorld.ir
ThereplacepipeThereplacepipeoperatesprettymuchliketheString.prototype.replace()functionoftheJavaScriptAPI,anditwillevaluateastringexpression,oranumberthatwillbetreatedasastringeitherway,againstagivenpattern.Allmatchesfoundwillbethenreplacedbyagivenstringreplacement.Wecanalsointroduceafunction,orareferencetoafunction,thatwillreceivethematchstringfound.Theoverallsyntaxisasfollows:
expression|replace:pattern:replacement
Itisimportanttonotethatthepatterncanbeconfiguredasaregularexpression.Infact,Angular2usesregularexpressionsunderthehoodtofindstringmatchessomakesureyouescapeanyspecialcharacterlikeparentheses,brackets,andsoon.
www.EBooksWorld.ir
Thei18npipesAspartofAngular'sstrongcommitmenttoprovidingastronginternationalizationtoolset,areducedsetofpipestargetingcommoni18nusecaseshavebeenmadeavailable.Thisbookwillonlycoverthetwomajorones,butitisquitelikelythatmorepipeswillbereleasedinthefuture.Pleaserefertotheofficialdocumentationforfurtherinformationafterfinishingthischapter.
Thei18nPluralpipe
Thei18nPluralpipehasasimpleusage,wherewejustevaluateanumericvalueagainstanobjectmappingdifferentstringvaluestobereturneddependingontheresultoftheevaluation.Thisway,wecanrenderdifferentstringsonourtemplatedependingifthenumericvalueiszero,one,two,morethanN,andsoon.Talkingaboutpomodoros,wecouldslidethisinourtemplate:
<h1>{{pomodoros|i18nPlural:pomodorosWarningMapping}}</h1>
Then,wecanhavethismappingasafieldofourcomponentcontrollerclass:
classMyPomodorosComponent{
pomodoros:number;
pomodorosWarningMapping:any={
'=0':'Nopomodorosfortoday',
'=1':'Onepomodoropending',
'other':'#pomodorospending'
}
}
Weevenbindthenumericvalueevaluatedintheexpressionbyintroducingthe'#'placeholderinthestringmappings.Whennomatchingvalueisfound,thepipewillfallbacktothemappingsetwiththekey'other'.
Thei18nSelectpipe
Thei18nSelectpipeissimilartoi18nPluralpipe,butevaluatesastringvalueinstead.Thispipeisperfectforlocalizingtextinterpolationsorprovidingdistinctlabelsdependingonstatechanges,forinstance.Forexample,wecouldrecaponourpomodorotimerandservetheUIindifferentlanguages:
<button(click)="togglePause()">
{{languageCode|i18nSelect:localizedLabelsMap}}
</button>
Inourcontrollerclass,wecanpopulatelocalizedLabelsMapasfollows:
classPomodoroTimerComponent{
languageCode:string='fr';
localizedLabelsMap:any={
'en':'Starttimer',
www.EBooksWorld.ir
'es':'Comenzartemporizador',
'fr':'Démarreruneséquence',
'other':'Starttimer'
}
...
}
Itisimportanttonotethatwecanputthisconvenientpipetouseinusecasesotherthanlocalisingcomponents,buttoprovidestringbindingsdependingonmapkeysandthelike.Sameasthei18nPluralpipe,whennomatchingvalueisfound,thepipewillfallbacktothemappingsetwiththekey'other'.
www.EBooksWorld.ir
TheasyncpipeSometimes,wemanageobservabledataoronlydatathatishandledasynchronouslybyourcomponentclass,andweneedtoensurethatourviewspromptlyreflectthechangesintheinformationoncetheobservablefieldchangesorasynchronousloadinghasbeenaccomplishedaftertheviewhasbeenrendered.Theasyncpipesubscribestoanobservableorpromiseandreturnsthelatestvalueithasemitted.Whenanewvalueisemitted,theasyncpipemarksthecomponenttobecheckedforchanges.
www.EBooksWorld.ir
PuttingitalltogetherinthePomodorotasklistNowthatyouhavelearnedalltheelementsthatallowyoutobuildfull-blowncomponents,it'stimetoputallofthisfreshknowledgeintopractice.Inthenextpageswearegoingtobuildasimpletasklistmanagerforourpomodoroapplication.Init,wewillseeataskstablecontainingtheto-doitemsweneedtoachieve:
Wewillalsoqueueuptasksstraightfromthebacklogoftasksavailable.Thiswillhelpshowingthetimerequiredtoaccomplishallthequeuedtasksandseehowmanypomodorosaredefinedinourworkingagenda.
www.EBooksWorld.ir
SettingupourmainHTMLcontainerBeforebuildingtheactualcomponentweneedtosetupourworkenvironmentfirstandinordertodosowewillreusethesameHTMLboilerplatefileweusedinthepreviouscomponent.Pleasesetasidetheworkyou'vedonesofarandkeepthepackage.json,tsconfig.json,typings.jsonandindex.htmlfilesweusedinpreviousexamples.Feelfreetoreinstallthemodulesrequiredincaseyouneedto,andreplacethecontentsofthebodytaginourindex.htmltemplate:
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroTasks</strong>
</div>
</div>
</nav>
<pomodoro-tasks></pomodoro-tasks>
Inanutshell,wehavejustupdatedthetitleoftheheaderlayoutaboveournew<pomodoro-tasks>customelements,whichreplacestheprevious<pomodoro-timer>.YoumightwanttoupdatetheconfigurationoftheSystem.import()commandtopointtoournewcompiledcomponentclass:
System.import('built/pomodoro-tasks')
.then(null,console.error.bind(console));
www.EBooksWorld.ir
BuildingourtasklisttablewithAngulardirectivesCreateanemptypomodoro-tasks.tsfile.Youmightwanttousethisnewlycreatedfiletobuildournewcomponentfromscratchandembedonitthedefinitionsofalltheaccompanyingpipes,directives,andcomponentswewillseelaterinthischapter.
Note
Real-lifeprojectsareneverimplementedthisway,sinceourcodemustconformtothe"oneclass,onefile"principle,takingadvantageofECMAScriptmodulesforgluingthingstogether.Chapter5,BuildinganApplicationwithAngular2ComponentswillintroduceyoutoacommonsetofgoodpracticesforbuildingAngular2applications,includingstrategiesfororganizingyourdirectorytreeandyourdifferentelements(components,directives,pipes,services,andsoon)inasustainableway.Thischapter,onthecontrary,willleveragepomodoro-tasks.tstoincludeallthecodeinacentrallocationandthenprovideabird'seyeviewofallthetopicswewillcovernowwithouthavingtogoswitchingacrossfiles.Bearinmindthatthisisinfactananti-pattern,butforinstructionalpurposeswewilltakethisapproachinthischapterforthelasttime.Theorderinwhichelementsaredeclaredwithinthefileisimportant.RefertothecoderepositoryinGitHubifexceptionsrise.
Beforemovingonwithourcomponent,weneedtoimportthedependenciesrequired,formalizethedatamodelwewillusetopopulatethetable,andthenscaffoldsomedatathatwillbeservedbyaconvenientserviceclass.
Let'sbeginbyaddingtoourpomodoro-tasks.tsfilethefollowingcodeblock,importingallthetokenswewillrequireinthischapter.PayspecialattentiontothetokensweareimportingfromtheAngular2library.WehavecoveredComponentandInputalready,butalltherestwillbeexplainedlaterinthischapter:
import{
Component,
Input,
Pipe,
PipeTransform,
Directive,
OnInit,
HostListener
}from'@angular/core';
import{bootstrap}from'@angular/platform-browser-dynamic';
Withthedependencytokensalreadyimported,let'sdefinethedatamodelforourtasks,nexttotheblockofimports:
///Modelinterface
interfaceTask{
name:string;
deadline:Date;
queued:boolean;
pomodorosRequired:number;
www.EBooksWorld.ir
}
TheschemaofaTaskmodelinterfaceisprettyself-explanatory.Eachtaskhasaname,adeadline,afieldinforminghowmanypomodorosneedtobeshipped,andaBooleanfieldnamedqueuedthatdefinesifthattaskhasbeentaggedtobedoneinournextpomodorosession.
Tip
Youmightbesurprisedthatwedefineamodelentitywithaninterfaceratherthanaclass,butthisisperfectlyfinewhentheentitymodeldoesnotfeatureanybusinesslogicrequiringimplementationofmethodsordatatransformationinaconstructororsetter/getterfunction.Whenthelatterisnotrequired,aninterfacejustsufficessinceitprovidesthestatictypingwerequireinasimpleandmorelightweightfashion.
Now,weneedsomedataandaservicewrapperclasstodeliversuchdataintheformofacollectionofTaskobjects.TheTaskServiceclassdefinedherewilldothetrick,soappendittoyourcoderightaftertheTaskinterface:
///LocalDataService
classTaskService{
publictaskStore:Array<Task>=[];
constructor(){
consttasks=[
{
name:"CodeanHTMLTable",
deadline:"Jun232015",
pomodorosRequired:1
},{
name:"Sketchawireframeforthenewhomepage",
deadline:"Jun242016",
pomodorosRequired:2
},{
name:"StyletablewithBootstrapstyles",
deadline:"Jun252016",
pomodorosRequired:1
},{
name:"ReinforceSEOwithcustomsitemap.xml",
deadline:"Jun262016",
pomodorosRequired:3
}
];
this.taskStore=tasks.map(task=>{
return{
name:task.name,
deadline:newDate(task.deadline),
queued:false,
pomodorosRequired:task.pomodorosRequired
};
});
}
www.EBooksWorld.ir
}
Thisdatastoreisprettyself-explanatory:itexposesataskStorepropertyreturninganarrayofobjectsconformingtotheTaskinterface(hencebenefitingfromstatictyping)withinformationaboutthename,deadline,andtimeestimateinpomodoros.
Nowthatwehaveadatastoreandamodelclass,wecanbeginbuildinganAngularcomponentwhichwillconsumethisdatasourcetorenderthetasksinourtemplateview.Insertthefollowingcomponentimplementationafterthecodeyouwrotebefore:
///Componentclasses
///-MainParentComponent
@Component({
selector:'pomodoro-tasks',
styleUrls:['pomodoro-tasks.css'],
templateUrl:'pomodoro-tasks.html'
})
classTasksComponent{
today:Date;
tasks:Task[];
constructor(){
constTasksService:TasksService=newTasksService();
this.tasks=taskService.taskStore;
this.today=newDate();
}
};
bootstrap(TasksComponent);
Asyoucansee,wehavedefinedandinstantiatedthroughthebootstrapfunctionanewcomponentnamedTasksComponentwiththeselector<pomodoro-tasks>(wealreadyincludeditwhenwewerepopulatingthemainindex.htmlfile,remember?).Thisclassexposestwoproperties:today'sdateandataskscollectionthatwillberenderedinatablecontainedinthecomponent'sview,aswewillseeshortly.Todoso,itinstantiatesinitsconstructorthedatasourcethatwecreatedpreviously,mappingittothearrayofmodelstypedasTaskobjectsrepresentedbythetasksfield.WealsoinitializethetodaypropertywithaninstanceoftheJavaScriptbuilt-inDateobject,whichcontainsthecurrentdate.
Tip
Asyouhaveseen,thecomponentselectordoesnotmatchitscontrollerclassnaming.Wewilldelvedeeperintonamingconventionsattheendofthischapter,asapreparationforChapter5,BuildinganApplicationwithAngular2Components.
Let'screatethestylesheetfilenow,whoseimplementationwillbereallysimpleandstraightforward.Createanewfilenamedpomodoro-tasks.cssatthesamelocationwhereourcomponentfilelives.Youcanthenpopulateitwiththefollowingstylesruleset:
www.EBooksWorld.ir
h3,p{
text-align:center;
}
table{
margin:auto;
max-width:760px;
}
Thisnewlycreatedstylesheetissosimplethatitmightseemabittoomuchtohaveitasastandalonefile.However,thiscomesasagoodopportunitytoshowcaseinourexamplethefunctionalitiesofthestyleUrlspropertyofthecomponentmetadata.
ThingsarequitedifferentinregardsofourHTMLtemplate.ThistimewewillnothardcodeourHTMLtemplateinthecomponenteither,butwewillpointtoanexternalHTMLfiletobettermanageourpresentationcode.PleasecreateanHTMLfileandsaveitaspomodoro-tasks.htmlinthesamelocationwhereourmaincomponent'scontrollerclassexists.Onceitiscreated,fillitinwiththefollowingHTMLsnippet:
<divclass="containertext-center">
<imgsrc="assets/img/pomodoro.png"alt="Pomodoro"/>
<divclass="container">
<h4>Tasksbacklog</h4>
<tableclass="table">
<thead>
<tr>
<th>TaskID</th>
<th>Taskname</th>
<th>Deliverby</th>
<th>Pomodoros</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr*ngFor="lettaskoftasks;leti=index">
<thscope="row">{{i}}</th>
<td>{{task.name|slice:0:35}}
<span[hidden]="task.name.length<35">...</span>
</td>
<td>{{task.deadline|date:'fullDate'}}
<span*ngIf="task.deadline<today"class="labellabel-danger">
Due
</span>
</td>
<tdclass="text-center">{{task.pomodorosRequired}}</td>
<td>
[Futureoptions...]
</td>
</tr>
</tbody>
</table>
</div>
</div>
WearebasicallycreatingatablethatfeaturesaneatstylingbasedontheBootstrap
www.EBooksWorld.ir
framework.Then,werenderallourtasksusingthealwaysconvenientNgFordirective,extractinganddisplayingtheindexofeachiteminourcollectionasweexplainedwhileoverviewingtheNgFordirectiveearlierinthischapter.
Pleaselookathowweformattedtheoutputofourtask'snameanddeadlineinterpolationsbymeansofpipes,andhowconvenientlywedisplay(ornot)anellipsistoindicatewhenthetextexceedsthemaximumnumberofcharactersweallocatedforthenamebyturningtheHTMLhiddenpropertyintoapropertyboundtoanAngularexpression.Allthispresentationlogicistoppedwitharedlabel,indicatingwhetherthegiventaskisduewheneveritsenddateispriortothisday.Ifyouexecutetheprecedingcode,thispagewillshowuponthescreen:
Youhaveprobablynoticedthatthoseactionbuttonsdonotexistinourcurrentimplementation.Wewillfixthisinthenextsection,playingaroundwithstateinourcomponents.BackinChapter1,CreatingOurVeryFirstComponentinAngular2,wetouchedupontheclickeventhandlerforstoppingandresumingthepomodorocountdown,andthendelveddeeperintothesubjectinChapter3,ImplementingPropertiesandEventsinOurComponents,wherewecoveredoutputproperties.Let'scontinueonourresearchandseehowwecanhookupDOMeventhandlerswithourcomponent'spublicmethods,addingarichlayerofinteractivitytoourcomponents.
www.EBooksWorld.ir
TogglingtasksinourtasklistAddthefollowingmethodtoyourTasksComponentcontrollerclass.Itsfunctionalityisprettybasic;wejustliterallytogglethevalueofthequeuedpropertyforagivenTaskobjectinstance:
toggleTask(task:Task):void{
task.queued=!task.queued;
}
Now,wejustneedtohookitupwithourviewbuttons.Updateourviewtoincludeaclickattribute(wrappedinbracessothatitactsasanoutputproperty)inthebuttoncreatedwithintheNgForloop.NowthatwewillhavedifferentstatesinourTaskobjects,let'sreflectthisinthebuttonlabelsbyimplementingaNgSwitchstructurealltogether:
<tableclass="table">
<thead>
<tr>
<th>TaskID</th>
<th>Taskname</th>
<th>Deliverby</th>
<th>Pomodoros</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr*ngFor="#taskoftasks;#i=index">
<thscope="row">{{i}}
<span*ngIf="task.queued"class="labellabel-info">
Queued
</span>
</th>
<td>{{task.name|slice:0:35}}
<span[hidden]="task.name.length<35">...</span>
</td>
<td>{{task.deadline|date:'fullDate'}}
<span*ngIf="task.deadline<today"class="labellabel-danger">
Due
</span>
</td>
<tdclass="text-center">{{task.pomodorosRequired}}</td>
<td>
<button
type="button"
class="btnbtn-defaultbtn-xs"
(click)="toggleTask(task)"
[ngSwitch]="task.queued">
<template[ngSwitchWhen]="false">
<iclass="glyphiconglyphicon-plus-sign"></i>
Add
</template>
<template[ngSwitchWhen]="true">
<iclass="glyphiconglyphicon-minus-sign"></i>
Remove
www.EBooksWorld.ir
</template>
<templatengSwitchDefault>
<iclass="glyphiconglyphicon-plus-sign"></i>
Add
</template>
</button>
</td>
</tr>
</tbody>
</table>
OurbrandnewbuttoncanexecutethetoggleTaskmethodinourcomponentclass,passingasanargumenttheTaskobjectthatcorrespondstothatiterationofNgFor.Ontheotherhand,theprecedingNgSwitchimplementationallowsustodisplaydifferentbuttonlabelsandiconsdependingonthestateoftheTaskobjectatanygiventime.
Tip
WearedecoratingthenewlycreatedbuttonswithfonticonsfetchedfromtheGlyphiconsfontfamily.TheiconsarepartoftheBootstrapCSSbundleweinstalledpreviouslyandareinnomeansrelatedtoAngular2.Feelfreetoskipitsuseortoreplaceitbyanothericonfontfamily.
Executethecodeasitisnowandcheckouttheresultsyourself.Neat,isn'tit?ButmaybewecangetmorejuicefromAngular2byaddingmorefunctionalitytothetasklist.
www.EBooksWorld.ir
DisplayingstatechangesinourtemplatesNowthatwecanpickthetaskstobedonefromthetable,itwouldbegreattohavesomekindofvisualhintofhowmanypomodorosessionswearemeanttoachieve.Thelogicisasfollows:
Theuserreviewsthetasksonthetableandpickstheonestobedonebyclickingoneachone.Everytimearowisclicked,theunderlyingTaskobjectstatechangesanditsBooleanqueuedpropertyistoggled.Thestatechangeisreflectedimmediatelyonthesurfacebydisplayinga"queued"labelontherelatedtaskitem.Theusergetspromptinformationoftheamountofpomodorosessionsrequiredandatimeestimationtodeliverthemall.Weseehowarowofpomodoroiconsaredisplayedabovethetable,displayingthesumofpomodorosfromallthetaskssettobedone.
ThisfunctionalitywillhavetoreacttothestatechangesofthesetofTaskobjectswe'redealingwith.ThegoodnewsisthatthankstoAngular2'sveryownchangedetectionsystem,makingcomponentsfullyawareofstatechangesisextremelyeasy.
Thus,ourveryfirsttaskwillbetotweakourTasksComponentclasstoincludesomewaytocomputeanddisplayhowmanytasksarequeuedup.Wewillusethatinformationtorenderornotablockofmarkupinourcomponentwherewewillinformhowmanypomodoroswehavelinedupandhowmuchaggregatedtimeitwilltaketoaccomplishthemall.
ThenewqueuedPomodorosfieldofourclasswillprovidesuchinformation,andwewillwanttoinsertanewmethodnamedupdateQueuedPomodoros()inourclassthatwillupdateitsnumericvalueuponinstantiatingthecomponentorenqueueingtasks.Ontopofthat,wewillcreateakey/valuemappingwecanuselaterontorenderamoreexpressivetitleheaderdependingontheamountofqueuedpomodorosthankstotheI18nPluralpipe:
classTasksComponent{
today:Date;
tasks:Task[];
queuedPomodoros:number;
queueHeaderMapping:any={
'=0':'Nopomodoros',
'=1':'Onepomodoro',
'other':'#pomodoros'
};
constructor(){
constTasksService:TasksService=newTasksService();
this.tasks=taskService.taskStore;
this.today=newDate();
this.updateQueuedPomodoros();
}
www.EBooksWorld.ir
toggleTask(task:Task):void{
task.queued=!task.queued;
this.updateQueuedPomodoros();
}
privateupdateQueuedPomodoros():void{
this.queuedPomodoros=this.tasks
.filter((task:Task)=>task.queued)
.reduce((pomodoros:number,queuedTask:Task)=>{
returnpomodoros+queuedTask.pomodorosRequired;
},0);
}
};
TheupdateQueuedPomodoros()methodmakesuseofJavaScript'snativeArray.filter()andArray.reduce()methodstobuildalistofqueuedtasksoutoftheoriginaltaskscollectionproperty.Thereducemethodappliedovertheresultingarraygivesusthetotalnumberofpomodorosrequired.Withastatefulcomputationofthenumberofqueuedpomodorosnowavailable,it'stimetoupdateourtemplateaccordingly.Gotopomodoro-tasks.htmlandinjectthefollowingchunkofHTMLrightbeforethe<h4>Tasksbacklog</h4>element.Thecodeisasfollows:
<div>
<h3>
{{queuedPomodoros|i18nPlural:queueHeaderMapping}}fortoday
<spanclass="small"*ngIf="queuedPomodoros>0">(Estimatedtime:{{
queuedPomodoros*25}})</span>
</h3>
</div>
<h4>Tasksbacklog</h4>
<!--restoftemplateremainsthesame-->
Theprecedingblockrendersaninformativeheadertitleatalltimes,evenwhennopomodoroshavebeenqueuedup.Wealsobindthatvalueinthetemplateanduseittoestimatethroughanexpressionbindingtheamountofminutesrequiredtogothrougheachandeverypomodorosessionrequired.
Tip
Wearehardcodingthedurationofeachpomodorointhetemplate.Ideally,suchconstantvalueshouldbeboundfromanapplicationvariableoracentralizedsetting.Don'tworry,wewillseehowwecanimprovethisimplementationinthenextchapters.
Saveyourchangesandreloadthepage,andthentrytotogglesometaskitemsonthetabletoseehowtheinformationchangesinrealtime.Exciting,isn'tit?
www.EBooksWorld.ir
EmbeddingchildcomponentsNow,let'sstartbuildingatinypomodoroiconcomponentthatwillbenestedinsidetheTasksComponentcomponent.Thisnewcomponentwilldisplayasmallerversionofourbigpomodoroicon,whichwewillusetodisplayonthetemplatetheamountofpomodoroslineduptobedone,aswedescribedearlierinthischapter.Let'spavethewaytowardscomponenttrees,whichwewillanalyzeindetailinChapter5,BuildinganApplicationwithAngular2Components.Fornow,justincludethefollowingcomponentclassbeforetheTasksComponentclassyoubuiltearlier:
OurcomponentwillexposeapublicpropertynamedtaskinwhichwecaninjectaTaskobject.ThecomponentwillusethisTaskobjectbindingtoreplicatetheimagerenderedinthetemplateasmanytimesaspomodorosessionsarerequiredbythistaskinitspomodorosRequiredproperty,allthisbymeansofaNgFordirective.
Inourpomodoro-tasks.tsfile,injectthefollowingblockofcodebeforeourTasksComponent:
@Component({
selector:'pomodoro-task-icons',
template:`<img*ngFor="leticonoficons"
src="/assets/img/pomodoro.png"
width="50">`
})
classTaskIconsComponentimplementsOnInit{
@Input()task:Task;
icons:Object[]=[];
ngOnInit(){
this.icons.length=this.task.pomodorosRequired;
this.icons.fill({name:this.task.name});
}
}
OurnewTaskIconsComponentfeaturesaprettysimpleimplementation,withaveryintuitiveselectormatchingitscamel-casedclassnameandatemplatewhereweduplicatethegiven<img>tagasmanytimesasobjectsarepopulatedintheiconsarraypropertyofthecontrollerclass,whichispopulatedwiththenativefillmethodoftheArrayobjectintheJavaScriptAPI(thefillmethodfillsalltheelementsofanarraywithastaticvaluepassedasanargument),withinngOnInit().Wait,whatisthis?Shouldn'tweimplementthelooppopulatingtheiconsarraymemberintheconstructorinstead?
Thismethodisoneofthelifecyclehookswewilloverviewinthenextchapter,andprobablythemostimportantone.Thereasonwhywepopulatetheiconsarrayfieldhereandnotintheconstructormethodisbecauseweneedeachandeverydata-boundpropertiestobeproperlyinitializedbeforeproceedingtoruntheforloop.Otherwise,itwillbetoosoontoaccesstheinputvaluetasksinceitwillreturnanundefinedvalue.
Tip
www.EBooksWorld.ir
TheOnInitinterfacedemandsanngOnInit()methodtobeintegratedinthecontrollerclassthatimplementssuch-interface,anditwillbeexecutedonceallinputpropertieswithabindingdefinedhavebeenchecked.Wewilltakeabird'seyeoverviewofcomponentlifecyclehooksinChapter5,BuildinganApplicationwithAngular2Components.
Still,ournewcomponentneedstofinditswaytoitsparentcomponent.So,let'sinsertareferencetothecomponentclassinthedirectivespropertyoftheTasksComponentdecoratorsettings:
@Component({
selector:'pomodoro-tasks',
directives:[TaskIconsComponent],
styleUrls:['pomodoro-tasks.css'],
templateUrl:'pomodoro-tasks.html'
})
Doyourememberthatwementionedthatcomponentsarebasicallydirectiveswithcustomviews?Ifso,thenwewillwanttousethedirectivespropertyofeachcomponenteverytimewewanttonestanothercomponentwithin.Thisexplainsthecaseforusingthedirectivespropertyhere.
Ournextstepwillbetoinjectthe<pomodoro-task-icons>elementintheTasksComponenttemplate.Gobacktopomodoro-tasks.htmlandupdatethecodelocatedinsidetheconditionalblockmeanttobedisplayedwhenqueuedPomodorosisgreaterthanzero.Thecodeisasfollows:
<div>
<h3>
{{queuedPomodoros|i18nPlural:queueHeaderMapping}}fortoday
<spanclass="small"*ngIf="queuedPomodoros>0">(Estimatedtime:{{
queuedPomodoros*25}})</span>
</h3>
<p>
<span*ngFor="letqueuedTaskoftasks">
<pomodoro-task-icons
[task]="queuedTask"
(mouseover)="tooltip.innerText=queuedTask.name"
(mouseout)="tooltip.innerText='Mouseoverfordetails'">
</pomodoro-task-icons>
</span>
</p>
<p#tooltip*ngIf="queuedPomodoros>0">Mouseoverfordetails</p>
</div>
<h4>Tasksbacklog</h4>
<!--restoftemplateremainsthesame-->
Thereisstillsomeroomforimprovementthough.Unfortunately,theiconsizeishardcodedintheTaskIconsComponenttemplateandthatmakesithardertoreusethatcomponentinothercontextswhereadifferentsizemightberequired.Obviously,wecouldrefactortheTaskIconsComponentclasstoexposeasizeinputpropertyandthenbindthevaluereceivedstraightintothecomponenttemplateinordertoresizetheimageaccordingly:
www.EBooksWorld.ir
@Component({
selector:'pomodoro-task-icons',
template:`<img*ngFor="leticonoficons"
src="/assets/img/pomodoro.png"
width="{{size}}">`
})
classTaskIconsComponentimplementsOnInit{
@Input()task:Task;
icons:Object[]=[];
@Input()size:number;
ngOnInit(){
...
}
}
Then,wejustneedtoupdatetheimplementationofpomodoro-tasks.htmltodeclarethevalueweneedforthesize:
<span*ngFor="letqueuedTaskoftasks">
<pomodoro-task-icons
[task]="queuedTask"
size="50"
(mouseover)="tooltip.innerText=queuedTask.name"
(mouseout)="tooltip.innerText='Mouseoverfordetails'">
</pomodoro-task-icons>
</span>
Pleasenotethatthesizeattributeisnotwrappedbetweenbracketsbecausewearebindingahardcodedvalue.Ifwewantedtobindacomponentvariable,thatattributeshouldbeproperlydeclaredas[size]="{{mySizeVariable}}".
Let'ssummarizewhatwedid:
WeinsertedanewDOMelementthatwillshowuponlywhenwehavepomodorosqueuedup.Wedisplayedanactualheadertellingushowmanypomodoroswearemeanttoachieve,bybindingthequeuedPomodorospropertyinanH3DOMelement,plusatotalestimationinminutesforaccomplishingallofthiscontainedinthe{{queuedPomodoros*25}}expression.TheNgFordirectiveallowsustoiteratethroughthetasksarray.Ineachiteration,werenderanew<pomodoro-task-icons>element.WeboundtheTaskmodelobjectofeachiteration,representedbythequeuedTaskreference,inthetaskinputpropertyofthe<pomodoro-task-icons>inthelooptemplate.Wetookadvantageofthe<pomodoro-task-icons>elementtoincludeadditionalmouseeventhandlersthatpointtothefollowingparagraph,whichhasbeenflaggedwiththe#tooltiplocalreference.So,everytimetheuserhoversthemouseoverthepomodoroicon,thetextbeneaththeiconsrowwilldisplaytherespectivepomodoro'staskname.
Werantheextramile,turningthesizeoftheiconrenderedby<pomodoro-task-icons>intoa
www.EBooksWorld.ir
configurablepropertyaspartofthecomponentAPI.Wenowhavepomodoroiconsthatgetupdatedinrealtimeaswetoggletheinformationonthetable.Newproblemshavearisen,however.Firstly,wearedisplayingpomodoroiconcomponentsmatchingtherequiredpomodorosofeachtask,withoutfilteringoutthosewhicharenotqueued.Ontheotherhand,theoverallestimationoftimerequiredtoachieveallourqueuedpomodorosdisplaysthegrossnumberofminutes,andthisinformationwillmakenosenseasweaddmoreandmorepomodorostotheworkingplan.
Perhaps,it'stimetoamendthis.It'sagoodthingthatcustompipeshavecometotherescue!
www.EBooksWorld.ir
BuildingourowncustompipesWehavealreadyseenwhatpipesareandwhattheirpurposeisintheoverallAngularecosystem,butnowwearegoingtodivedeeperintohowwecanbuildourownsetofpipestoprovidecustomtransformationstodatabindings.
www.EBooksWorld.ir
AnatomyofacustompipePipesareveryeasytodefine.Firstofall,weneedtoimportthePipedecoratorfromtheAngularcorelibraryandcreateanewclassdecoratedwiththisdecorator.ThisnewclasshastobenamedwithourselectorofchoiceinthedecoratorconfigurationandimplementthePipeTransforminterface.
Theclassimplementationisprettysimpleaswell.ItjustconsistsofamandatorymethodrequiredbythePipeTransforminterface,namedtransform,whichwillreturnatypeofourchoice(usuallythetypecorrespondingtotheinputthatwefeedthepipewith)andtwoparameters.Theinputitselfisthefirstparameter,followedbyanoptionalspreadargument(refertoChapter2,IntroducingTypeScripttolookintospreadargumentsinTypeScript)containingthesettingsthatconfigurethepipeinourview:
import{Pipe,PipeTransform}from'@angular/core';
@Pipe({
name:'myPipeName',
pure:false//optional,defaultistrue
})
classMyPipeimplementsPipeTransform{
transform(value:any,...args:any[]):any{
//Weapplytransformationstotheinputvaluehere
returnsomething;
}
}
@Component({
selector:'my-selector',
pipes:[MyPipe],
template:'<p>{{myVariable|myPipeName:"bar"}}</p>'
})
classmyComponent{
myVariable:string='Foo';
}
Tip
Intheprecedingexample,wecreatedanimpurepipenamedMyPipethatwouldapplysometransformationstoitsinputaccordingtotheparametersprovidedwhenapplyingitinthecomponentdefinednext.
Justaswithcustomdirectives,componentsmustexplicitlystateinthepipespropertywhatcustompipesareimplementing.
RegardingtheoptionalpurepropertyinthePipedecorator,wemustclarifythatpipesarestateless.Thismeansthataninstanceofapipewillbereusedandthepipewillbecalledonlywhenitsargumentschange.Inotherwords,whenthepipetransformstheinput,itdisregardstheoriginalinputandfocusesonthecopythatwasjustmade.Iftheoriginalinputchangeslateron,thechangeswillnotbereflectedintheview.Fortunately,wecanenablethestateon
www.EBooksWorld.ir
pipesthroughthepureBooleanproperty.Whensettofalse,thepipewillkeepthestateofthevaluesittransforms,anditwillparseandtransformtheunderlyingexpressionagainassoonasAngular'schangedetectionsystemchecksthatthesourcedatahaschanged.
Let'sputthisconcepttoworkbycreatingacoupleofcustompipesforourcomponent.
www.EBooksWorld.ir
AcustompipetobetterformattimeoutputWatchingthegrossnumberofminutessummedupwhenlininguptaskstobedoneisnotveryintuitive,soweneedawaytodeconstructthisvalueintohoursandminutes.OurpipewillhavethenamepomodoroFormattedTimeandwillbeimplementedbytheFormattedTimePipeclass,whoseuniquetransformmethodreceivesanumberrepresentingatotalnumberofminutesandreturnsastring(provingthatpipesdonotneedtoreturnthesametypeastheyreceiveinthepayload)inareadabletimeformat:
@Pipe({
name:'pomodoroFormattedTime'
})
classFormattedTimePipeimplementsPipeTransform{
transform(totalMinutes:number):string{
letminutes:number=totalMinutes%60;
lethours:number=Math.floor(totalMinutes/60);
return`${hours}h:${minutes}m`;
}
}
Tip
WeshouldnotskiptheopportunitytohighlightthatthenamingconventionforPipesis,sameaswesawwithComponents,thenameofthepipeclasswiththePipesuffixplusaselectormatchingthatnamewithoutthesuffix.Thedifferencehereisthatwerepresentthepipeselectorincamelcaseandprefixitwithpomodoroforourexample.Whythismismatchbetweenthepipecontroller'sclassnameandtheselector?Itiscommonpracticetoprefixtheselectorstringsofourcustompipesanddirectiveswithacustomprefixinordertopreventcollisionswithotherselectorsdefinedbythirdpartypipesanddirectives.
Pleaserememberthatcustompipesdonotbecomeavailableinourtemplatesautomatically;theyhavetobeexplicitlydeclaredinthepipespropertyofthedecoratorconfigurationofeachcomponentthatwantstousethem.Inourcase,itistheTasksComponent:
@Component({
selector:'pomodoro-tasks',
directives:[PomodoroIconComponent],
pipes:[FormattedTimePipe],
styleUrls:['pomodoro-tasks.css'],
templateUrl:'pomodoro-tasks.html'
})
classPomodoroTasksComponent{
//Classimplementationremainsthesame
}
Finally,wejustneedtotweaktheHTMLinthepomodoro-tasks.htmltemplatefiletoensurethatourEDTexpressionisproperlyformatted:
<spanclass="small">
(Estimatedtime:{{queuedPomodoros*25|pomodoroFormattedTime}})
www.EBooksWorld.ir
</span>
Nowreloadthepageandtogglesometasks.Theestimatedtimewillbeproperlyrenderedinhoursandminutes.
www.EBooksWorld.ir
FilteringoutdatawithcustomfiltersAswenoticedalready,wearedisplayingatthismomentapomodoroiconcomponentforeachandeverytaskinthecollectionservedfromthetasksservice,withoutfilteringoutwhattasksaremarkedasqueuedandwhicharen't.Pipesprovideaconvenientwaytomap,transformanddigestdatabindings,sowecanleverageitsfunctionalitiesforfilteringoutthetasksbindinginourNgForlooptoreturnonlythosetasksthataremarkedasqueued.
Thelogicwillbeprettysimple:sincethetasksbindingisanarrayofTaskobjects,wejustneedtomakeuseoftheArray.filter()methodtofetchonlythoseTaskobjectswhosequeuedpropertyissettotrue.WemightruntheextramileandconfigureourpipetotakeoneBooleanargumentindicatingwhetherwewanttofilteroutqueuedorunqueuedtasks.Theimplementationoftheserequirementsisasfollows,whereyoucanseeagaintheconventionsinplacefortheselectorandclassnames:
@Pipe({
name:'pomodoroQueuedOnly',
pure:false
})
classQueuedOnlyPipeimplementsPipeTransform{
transform(task:Task],...args:any[]):Task[]{
returntasks.filter((task:Task)=>{
returntask.queued===args[0];
});
}
}
Theimplementationisprettystraightforward,sowewillnotgetintodetailaboutithere.However,thereissomethingthatisworthhighlightingatthisstage:thisisanimpurepipe.Bearinmindthatthetasksbindingisacollectionofstatefulobjectsthatwillchangeinlengthandcontentastheusertogglestasksonthetable.Forthatreason,weneedtoinstructthepipetotakeadvantageofAngular'schangedetectionsystemsoitsoutputischeckedbythelatteroneverycycleregardlessofwhetheritsinputhaschangedornot.Configuringthepurepropertyofthepipedecoratorasfalsewilldothetrickthen.
Now,wejustneedtoupdatethepipespropertyofthecomponentusingthispipe:
@Component({
selector:'pomodoro-tasks',
directives:[PomodoroIconComponent],
pipes:[FormattedTimePipe,QueuedOnlyPipe],
styleUrls:['pomodoro-tasks.css'],
templateUrl:'pomodoro-tasks.html'
})
classPomodoroTasksComponent{
//Classimplementationremainsthesame
}
Then,updatetheNgForblockinpomodoro-tasks.htmltoproperlyfilterouttheunqueued
www.EBooksWorld.ir
tasks:
<span*ngFor="#queuedTaskoftasks|pomodoroQueuedOnly:true">
<pomodoro-task-icons
[task]="queuedTask"
(mouseover)="tooltip.innerText=queuedTask.name"
(mouseout)="tooltip.innerText='Mouseoverfordetails'">
</pomodoro-task-icons>
</span>
PleasecheckhowweconfiguredthepipeaspomodoroQueuedOnly:true.ReplacingtheBooleanparametervaluebyfalsewillgiveusthechancetoenlistthepomodorospertainingtothequeueswehavenotpicked.
Saveallyourworkandreloadthepage,togglingsometasksthen.YouwillseehowouroverallUIreactstothelatestchangesaccordingly,andweonlyenlistthepomodoroiconspertainingtotheamountofpomodorosrequiredofqueuedtasksonly.
www.EBooksWorld.ir
BuildingourowncustomdirectivesCustomdirectivesencompassavastworldofpossibilitiesandusecases,andwewouldneedanentirebookforshowcasingalltheintricaciesandpossibilitiestheyoffer.
Inanutshell,directivesallowyoutoattachadvancedbehaviorstoelementsintheDOM.Ifadirectivehasatemplateattached,thenitbecomesacomponent.Inotherwords,componentsareAngulardirectiveswithaview,butwecanbuilddirectiveswithnoattachedviewsthatwillbeappliedtoalreadyexistingDOMelements,makingitsHTMLcontentsandstandardbehaviorimmediatelyaccessibletothedirective.ThisappliestoAngularcomponentsaswell,wherethedirectivewilljustaccessitstemplateandcustomattributesandeventswhennecessary.
www.EBooksWorld.ir
AnatomyofacustomdirectiveDeclaringandimplementingacustomdirectiveisprettyeasy.WejustneedtoimporttheDirectiveclasstoprovidedecoratorfunctionalitiestoitsaccompanyingcontrollerclass:
import{Directive}from'@angular/core';
ThenwedefineacontrollerclassannotatedbytheDirectivedecorator,wherewewilldefinethedirectiveselector,inputandoutputproperties(ifrequired),optionaleventsappliedtothehostelement,andinjectableprovidertokens,shouldourdirective'sconstructorrequirespecifictypestobeinstantiatedbytheAngular2injectorwheninstancingitself(wewillcoverthisindetailinChapter5,BuildinganApplicationwithAngular2Components):
@Directive({
selector:'[selector]',
inputs:['inputPropertyName'],
outputs:['outputPropertyName'],
host:{
'(event1)':'onMethod1($event)',
'(target:event2)':'onMethod2($event)',
'[prop]':'expression',
'attributeName':'attributeValue'
},
providers:[MyCustomType]
})
classmyDirective{
@Input()otherInputPropertyName:any;
@Output()otherOutputPropertyName:any;
constructor(myCustomType:MyCustomType){
//implementation...
}
}
Propertiesanddecorators'suchasselector,@Input(),or@Output()(samewithinputsandoutputs)willprobablyresonatetoyoufromthetimewhenweoverviewedthecomponentdecoratorspec.Althoughwehaven'tmentionedallthepossibilitiesindetailyet,theselectormaybedeclaredasoneofthefollowing:
element-name:Selectbyelementname.class:Selectbyclassname[attribute]:Selectbyattributename[attribute=value]:Selectbyattributenameandvaluenot(sub_selector):Selectonlyiftheelementdoesnotmatchthesub_selectorselector1,selector2:Selectifeitherselector1orselector2matches
Inadditiontothis,wewillfindthehostparameter,whichspecifiestheevents,actions,properties,andattributespertainingtothehostelement(thatis,theelementwhereourdirectivetakesaction)thatwewanttoaccessfromwithinthedirective.Wecanthereforetakeadvantageofthisparametertobindinteractionhandlersagainstthecontainercomponentor
www.EBooksWorld.ir
anyothertargetelementofourchoice,suchaswindow,document,orbody.Inthisway,wecanrefertotwoveryconvenientlocalvariableswhenwritingadirectiveeventbinding:
$event:Thisisthecurrenteventobjectthattriggeredtheevent.$target:Thisisthesourceoftheevent.ThiswillbeeitheraDOMelementoranAngulardirective.
Besidesevents,wecanupdatespecificDOMpropertiesthatbelongtothehostcomponent.Wejustneedtolinkanyspecificpropertywrappedinbraceswithanexpressionhandledbythedirectiveasakey-valuepairinourdirective'shostdefinition.
Note
Theoptionalhostparametercanalsospecifystaticattributesthatshouldbepropagatedtoahostelement,ifnotpresentalready.ThisisaconvenientwayofinjectingHTMLpropertieswithcomputedvalues.
TheAngularteamhasalsomadeavailableacoupleofconvenientdecoratorssothatwecanmoreexpressivelydeclareourhostbindingsandlistenersstraightonthecode,likethis:
@HostBinding('[class.valid]')
isValid:boolean;//Thehostelementwillfeatureclass="valid"
//isthevalueof'isValid'istrue.
@HostListener('click',['$event'])
onClick(e){
//Thisfunctionwillbeexecutedwhenthehost//componenttriggersa
'click'event.
}
Inthenextchapters,wewillcovertheconfigurationinterfaceofdirectivesandcomponentsinmoredetail,payingspecialattentiontoitslifecyclemanagementandhowwecaneasilyinjectdependenciesintoourdirectives.Fornow,let'sjustbuildasimple,yetpowerful,directivethatwillmakeahugedifferencetohowourUIisdisplayedandmaintained.
www.EBooksWorld.ir
BuildingatasktooltipcustomdirectiveLet'sputinpracticesomeofthesettingsdescribedaboveinacustomdirective.Sofar,wehavebeendisplayingatooltiptextuponhoveringoverourpomodoroicons.Todoso,weattachedapairofeventbindingstothe<pomodoro-task-icons>element.Whilethisapproachisnotwrong,theoutputisabitverboseandnotreusableatall.Atsomepointwemayevenneedtoapplythesame[task]bindingelsewhereaswellandtakingadvantageofthesametooltiponmouseoverwouldbequiteconvenient.Let'sautomatesuchfunctionalityinadirectivethatwillgetautomaticallyappliedtoanyelementfeaturinga[task]attribute,asour<pomodoro-task-icons>elementsdo.Thisdirectivewilldefineinputpropertiestorefertothatverysamepropertybindingandalsothetargetelementwewilluseasaplaceholder.Ifnotavailable,thedirectivewilljustdonothingandwillnotyieldanyexceptionwhatsoever.Whenavailable,thedirectivewillbindmouseoverandmouseouteventlistenerstothehostelement(<pomodoro-task-icons>inourexample).TheselistenerswilltogglethetextinsidetheDOMelementrepresentedbythelocalreferenceboundtotheplaceholderproperty.Beforedoingso,wewillcachetheoriginalvalueinordertoreuseituponmovingthemouseoutfromtheelement.
Theprecedingdescriptiontakesforminthefollowingdirectivethatyoushouldimplementbeforeourcomponentsinthepomodoro-tasks.tsfile:
@Directive({
selector:'[task]'
})
classTaskTooltipDirective{
privatedefaultTooltipText:string;
@Input()task:Task;
@Input()taskTooltip:any;
@HostListener('mouseover')
onMouseOver(){
if(!this.defaultTooltipText&&this.taskTooltip){
this.defaultTooltipText=this.taskTooltip.innerText;
}
this.taskTooltip.innerText=this.task.name;
}
@HostListener('mouseout')
onMouseOut(){
if(this.taskTooltip){
this.taskTooltip.innerText=this.defaultTooltipText;
}
}
}
Pleasenotetheselectorinuse:[task].Wehavenotconfiguredthemorelogical<pomodoro-task-icons>elementorcreatedanewselectorofourown.Weobviouslycouldhavedonethat,butourgoalinthisexerciseisdifferent.WewanttobindaspecialbehaviortoanyDOMelementandcomponentthatfeaturesa[task]attributewithadatabindingonit.Preciselybecausethisdirectivewilltakeactiononallelementsfeaturingsuchproperty,wecaninclude
www.EBooksWorld.ir
itasaninputpropertyinthedirectiveimplementationitself.ThenwejustneedtoprovideawaytoconfigurewhatDOMelementwillbecomeourtooltipplaceholderwiththetaskTooltipinputpropertyandweareallset.
Aswesawintheprevioussection,thankstothe@HostListener()decorators,wecanbindalistenerfunctioninourdirectivetoaneventoccurredinthehostcomponent.Thistimeweboundthemouseoverandmouseouteventsotogglethetextofthetargettooltipplaceholder,cachingitscurrenttextbeforehand.
Inordertoseethisdirectiveinaction,weneedtoaddsupportforitfirstatthePomodoroTasksComponentdecorator:
@Component({
selector:'pomodoro-tasks',
directives:[PomodoroIconComponent,TaskTooltipDirective],
pipes:[FormattedTimePipe,pomodoroQueuedOnlyPipe],
styleUrls:['pomodoro-tasks.css'],
templateUrl:'pomodoro-tasks.html'
})
classPomodoroTasksComponent{
//Nomorechangesapply
}
Now,wecanupdateourpomodoro-tasks.htmltemplate:
<p>
<span*ngFor="#queuedTaskoftasks|pomodoroQueuedOnly:true">
<pomodoro-task-icons
[task]="queuedTask"
[taskTooltip]="tooltip"
size="50">
</pomodoro-task-icons>
</span>
</p>
<p#tooltip>Mouseoverfordetails</p>
Oneofthemostexcitingtakeawaysofthiscodeexampleisthelowcodefootprintrequiredforextendingelementswiththisnewfunctionality,anditshugereusability.
Afterallthelatestchanges,reloadyourbrowser,toggleanytask,moveyourmouseoverthenewlyrenderedpomodoroiconand...voilà!
www.EBooksWorld.ir
AwordaboutnamingconventionsforcustomdirectivesandpipesTalkingaboutreusability,thecommonconventionistoprependacustomprefixtotheselector.Thispreventsconflictswithotherselectorsdefinedbyotherlibrarieswemightbeusinginourproject.SameappliestoPipesaswell,aswehighlightedalreadywhenintroducingourveryfirstcustompipe.
WehaveusedpomodoroasourcustomprefixandwillkeeponusingitthroughoutthebookbutIadvisetouseashorterbutrecognizableprefixinyourcustomdirectivesandpipes'selectors.
Ultimately,itisuptoyouandthenameconventionyouembracebutitisgenerallyagoodideatoestablishanamingconventionthatpreventsthisfromhappening.Acustomprefixisdefinitelytheeasierway.
www.EBooksWorld.ir
SummaryNowthatwehavereachedthispoint,itisfairtosaythatyouknowalmosteverythingittakestobuildAngular2components,whichareindeedthewheelsandtheengineofallAngular2applications.Intheforthcomingchapters,wewillseehowwecandesignourapplicationarchitecturebetter,andthereforemanagedependencyinjectionthroughoutourcomponentstree,consumedataservices,leveragethenewAngularroutertoshowandhidecomponentswhenrequired,andmanageuserinputandauthentication.
Nevertheless,thischapteristhebackboneofAngular2development,andwehopethatyouenjoyeditasmuchaswedidwhenwritingabouttemplatesyntax,componentAPIsbasedonpropertiesandevents,viewencapsulation,pipes,anddirectives.Now,getreadytoassumenewchallenges—weareabouttomovefromlearninghowtowritecomponentstodiscoveringhowwecanusethemtobuildbiggerapplications,whileenforcinggoodpracticesandrationalarchitectures.Wewillseeallthisinthenextchapter.
www.EBooksWorld.ir
Chapter5.BuildinganApplicationwithAngular2ComponentsWehavereachedapointinourjourneywherewecansuccessfullydevelopmorecomplexapplicationsbynestingcomponentswithinothercomponents,inasortofcomponenttree.However,bundlingallourcomponentlogicinauniquefileisdefinitelynotthewaytogo.Ourapplicationmightbecomeunmaintainableverysoonand,aswewillseelaterinthechapter,wewouldbemissingtheadvantagesthatAngular'sdependencymanagementmechanismcanbringtothegame.
Inthischapter,wewillseehowtobuildapplicationarchitecturesbasedontreesofcomponents,andhowthenewAngular2dependencyinjectionmechanismwillhelpustodeclareandconsumeourdependenciesacrosstheapplicationwithminimumeffortandoptimalresults.
Inthischapter,wewillcoverthesetopics:
BestpracticesfordirectorystructuresandnamingconventionsDifferentapproachestodependencyinjectionInjectingdependenciesintoourcustomtypesOverridingglobaldependenciesthroughoutthecomponenttreeInteractingwiththehostcomponentOverviewingthedirectivelifecycle
www.EBooksWorld.ir
IntroducingthecomponenttreeModernwebapplicationsbasedonwebcomponentarchitecturesoftenconformtoasortoftreehierarchy,whereinthetopmaincomponent(usuallydroppedsomewhereinthemainHTMLindexfile)actsasaglobalplaceholderwherechildcomponentsturnintohostsforothernestedchildcomponents,andsoonandsoforth.
Thereareobviousadvantagestothisapproach.Ononehand,reusabilitydoesnotgetcompromisedandwecanreusecomponentsthroughoutthecomponenttreewithlittleeffort.Secondly,theresultinggranularityreducestheburdenrequiredforenvisioning,designing,andmaintainingbiggerapplications.WecansimplyfocusonasinglepieceofUIandthenwrapitsfunctionalityaroundnewlayersofabstractionuntilwewrapupafull-blownapplicationfromthegroundup.
Alternatively,wecanapproachourwebapplicationtheotherwayaround,andstartfromamoregenericfunctionalityjusttoendupbreakingdowntheappintosmallerpiecesofUIandfunctionality,whichbecomeourwebcomponents.Thelatterhasbecomethemostcommonapproachwhenbuildingcomponent-basedarchitectures.Wewillsticktoitfortherestofthebook,undertakingarchitecturesastheonedepictedhere:
Applicationbootstrap
└──Applicationcomponent
├──ComponentA
├──ComponentB
│├──ComponentB-I
│└──ComponentB-II
├──ComponentC
└──ComponentD
Forthesakeofclarity,thischapterwilljustborrowthecodewewroteinthepreviouschapters,andwewilldeconstructitintoacomponenthierarchy.Wewillalsoallocatesomeroomintheresultingapplicationforallthesupportingclassesandmodelsrequiredtogiveshapetoourpomodorotool.ThiswillturnintoaperfectopportunitytolearntheintricaciesofthedependencyinjectionmachinerybakedintoAngular2,aswewillseelaterinthischapter.
www.EBooksWorld.ir
CommonconventionsforscalableapplicationsInallfairness,wehavealreadytackledagoodnumberofthecommonconcernsthatmodernwebdevelopersconfrontwhenbuildingapplications,smallandlargealike,nowadays.Therefore,itmakessensetodefineanarchitecturethatwillseparatetheaforementionedconcernsintoseparatedomainfolders,cateringtomediaassetsandsharedcodeunits.
Atthetimeofwriting,acommonlyagreedpatternfordefiningprojectdirectoriesembracestheideaofstructuringfilesbyfeatures,orcontexts.Sometimes,twocontextsmayrequiresharingthesameentities,andthatisfine(aslongasitdoesnotbecomeacommonthinginourproject,whichwoulddenoteaseriousdesignissue).Thefollowingexample,appliedtoourpreviousworkonpomodorocomponents,depictsthisscheme:
.
├──tasksfeature
│├──Taskmodel
│├──Tasksservice
│├──Tasktablecomponent
│├──
Taskpomodoroscomponent
│└──Tasktooltipdirective
├──timerfeature
│└──Timercomponent
├──admin
│├──Authenticationservice
│├──Logincomponent
│└──Editorcomponent
└──shared
├──componentssharedacrossfeatures
├──pipessharedacrossfeatures
├──directivessharedacrossfeatures
├──globalmodelsandservices
└──sharedmediaassets
Aswecansee,thefirststepistodefinethedifferentfeaturesourapplicationneeds,keepinginmindthateachoneshouldmakesenseonitsowninisolationfromtheothers.Oncewedefinethesetoffeaturesrequired,wewillcreateafolderforeachone.Eachfolderwillbefilledthenwiththecomponents,directives,pipes,models,andservicesthatshapethefeatureitrepresents.Alwaysremembertheprinciplesofencapsulationandreusabilitywhendefiningyourfeaturesset.
Ifthenumberoffilesrequiredforanygivenfeatureexceedsalogicalthreshold,thenitisfinetoorganizethingsabitandsplitourfilesintodifferentfoldersbytype.Let'sfigureoutthatourtasksfeaturehasgrownoutofcontrolandwehaveupto30filesinthefolder,betweencomponents,directives,pipes,services,testspecs,andthelike.Identifyingcodeunitswould
www.EBooksWorld.ir
becomeaburden,soapplyinganadditionallayerororganizationbytypewoulddefinitelyhelp:
.
├──tasksfeature
│├──components/
││└──componentfiles...
│├──directives/
││├──directiveX
││└──directiveY
│├──pipes/
││└──pipefiles...
│├──models/
││└──models...
│└──services/
│└──services...
├──timerfeature
│└──Timercomponent
├──admin
│├──Authenticationservice
│├──Logincomponent
│└──Editorcomponent
└──shared
├──...
└──Etc
Aswecansee,itisperfectlyfinetohaveinthesameprojectfeaturefolderswithallfilesatthesamelevelandfeaturefolderscontaininganadditionallevelofnestingbytype.Wheretosetthethresholdisuptoyoubutcommonsensesaysthat12or15codeunitsinthesamefeaturefoldermakeagoodcaseforanadditionalnestinglevelbasedontypes.Wecanalsocombinethembyintroducingadditionalnestinglevelsbasedonmultiplefeaturesthatfallundertheumbrellaofanuppercontextaswell,hostingitsimplementationinasub-treebytype:
.
├──tasksfeature
│├──taskscomponentandtemplate
│├──tasks-editorfeature/
││└──task-editorcomponentsandtemplates
│├──tasks-listfeature/
││├──components/
│││└──tasks-listcomponentsandtemplates
││└──pipes/
│││└──tasks-list-specificpipes
│└──task-reportsfeature/
│└──services...
...
www.EBooksWorld.ir
FileandmodulenamingconventionsEachoneofourfeaturefolderswillhostawiderangeoffilessoweneedaconsistentnamingconventiontopreventfilenamecollisionswhileweensurethatthedifferentcodeunitsareeasytolocate.
Thefollowinglistsummarizesthecurrentconventionsenforcedbythecommunity:
Eachfileshouldcontainasinglecodeunit.Simplyput,eachcomponent,directive,service,pipe,andsoonshouldliveinitsownfile.Thisway,wecontributetoabetterorganizationofcode.Filesanddirectoriesarenamedinlower-kebab-case.Filesrepresentingcomponents,directives,pipes,andservicesshouldappendatypesuffixtotheirname:video-player.tswillbecomevideo-player.component.ts.Anycomponent'sexternalHTMLtemplateorCSSstylesheetfilenamewillmatchthecomponentfilename,includingthesuffix.Ourvideo-player.component.tsmightbeaccompaniedbyvideo-player.component.cssandvideo-player.component.html.DirectiveselectorsandpipenamesarecamelCased,whilecomponentselectorsarelower-kebab-cased.Plus,itisstronglyadvisedtoaddacustomprefixofourchoicetopreventnamecollisionswithothercomponentlibraries.Forexample,followingupourvideoplayercomponent,itmayberepresentedas<vp-video-player>,wherevp-(whichstandsforvideo-player)isourcustomprefix.ModulesarenamedbyfollowingtheruleoftakingaPascalCasedself-descriptivename,plusthetypeitrepresents.Forexample,ifweseeamodulenamedVideoPlayerComponent,wecaneasilytellitisacomponent.Thecustomprefixinuseforselectors(vp-inourexample)shouldnotbepartofthemodulename.
Modelsandinterfacesrequirespecialattentionthough.Dependingonyourapplicationarchitecture,modeltypeswillfeaturemoreorlessrelevance.ArchitecturessuchasMVC,MVVM,Flux,orReduxtacklemodelsfromdifferentstandpointsandgradesofimportance.Ultimately,itwillbeuptoyouandyourarchitecturaldesignpatternofchoicetoapproachmodelsandtheirnamingconventioninonewayoranother.Thisbookwillnotbeopinionatedinthatsense,althoughwedoenforceinterfacemodelsinourexampleapplicationandwillcreatemodulesforthem.
www.EBooksWorld.ir
EnsuringseamlessscalabilitywithfacadesorbarrelsEachcomponentandsharedcontextofbusinesslogicinourapplicationisintendedtointegratewiththeotherpiecesofthepuzzleinasimpleandstraightforwardway.Clientsofeachsubdomainarenotconcernedabouttheinternalstructureofthesubdomainitself.Ifourtimerfeature,forexample,evolvestothepointofhavingtwodozencomponentsanddirectivesthatneedtobereorganizedintodifferentfolderlevels,externalconsumersofitsfunctionalitiesshouldremainunaffected.
Thiscanbedoneusingfacademodulesthatconcealtheinternalstructureofthecodebyexposingalwaysthesamelayerofendpoints,hidingtheimplementationdetailsoutsidetheboundariesofthefeature.
Inourpreviousexample,thetasksfeatureevolvesfrombeingahandfuloffilesinsidethesamefolderintoatype-drivensetoffolders.Whatifweareusingseveralofitscomponentselsewhereinourapplication?Noworries!Afacademodulelikethiswoulddefinitelyhelp:
app/tasks/tasks.ts
importTaskComponentfrom'./task.component';
importTaskDetailsComponentfrom'./task-details.component';
export{
TaskComponent,
TaskDetailsComponent
}
Then,wecanimportanyofthesecomponentsfromanyotherdistantcornerofourapplication,likethis:
app/example/example.ts
import{TaskDetailsComponent}from'../tasks/tasks'
Whatifthetasksfeaturekeepsgrowingandneedstoberefactoredintodifferentfolders?Wewouldjustupdatethepathreferencesinourapp/tasks/tasks.tsfacademodule,andourclientcodeatapp/example/example.tswouldremainthesame.
IntheAngularlingo,thiskindofdesignpatternalsoreceivesthenameofbarrel.InAngular'sownwords:
AbarrelisanAngularlibrarymoduleconsistingofalogicalgroupingofsingle-purposemodulessuchasComponentandDirective.
Takethatintoaccountinordertoavoidconfusionwhenbumpingintothisterminthefuture.Barrelsarealsousuallygroupedanddistributedinlargerpackagesnamedbundles.Anexampleofthisistheangular2/bundles/router.jsbundle,forinstance.Wewillnotcreate
www.EBooksWorld.ir
packagedbundlesinthisbook,butwewillthoroughlyusetheonesthatcomewithAngular2whenimplementingHTTPconnection,routing,oranimationfunctionalities.
www.EBooksWorld.ir
HowdependencyinjectionworksinAngular2Asourapplicationsgrowsandevolves,eachoneofourcodeentitieswillinternallyrequireinstancesofotherobjects,whicharebetterknownasdependenciesintheworldofsoftwareengineering.Theactionofpassingsuchdependenciestothedependentclientisknownasinjection,anditalsoentailstheparticipationofanothercodeentity,namedtheinjector.Theinjectorwilltakeresponsibilityforinstantiatingandbootstrappingtherequireddependenciessotheyarereadyforusefromtheverymomenttheyaresuccessfullyinjectedintheclient.Thisisveryimportantsincetheclientknowsnothingabouthowtoinstantiateitsowndependenciesandisonlyawareoftheinterfacetheyimplementinordertousethem.
Angular2featuresatop-notchdependencyinjectionmechanismtoeasethetaskofexposingrequireddependenciestoanyentitythatmightexistinanAngular2application,regardlessofwhetheritisacomponent,adirective,apipe,oranyothercustomserviceorproviderobject.Infact,aswewillseelaterinthischapter,anyentitycantakeadvantageofdependencyinjection(usuallyreferredtoasDI)inanAngular2application.Beforedelvingdeeperintothesubject,let'slookattheproblemthatAngular'sDIistryingtoaddress.
Let'sfigureoutwehaveamusicplayercomponentthatreliesonaplaylistobjecttobroadcastmusictoitsusers:
import{Component}from'@angular/core';
import{Playlist}from'./playlist';
@Component({
selector:'music-player',
templateUrl:'./music-player.component.html'
})
classMusicPlayerComponent{
playlist:Playlist;
constructor(){
this.playlist=newPlaylist();
}
}
ThePlaylisttypecouldbeagenericclassthatreturnsinitsAPIarandomlistofsongsorwhatever.Thatisnotrelevantnow,sincetheonlythingthatmattersisthatourMusicPlayerComponententitydoesneedittodeliveritsfunctionality.Unfortunately,theimplementationabovemeansthatbothtypesaretightlycoupled,sincethecomponentinstantiatestheplaylistwithinitsownconstructor.Thispreventsusfromaltering,overriding,ormockingupinaneatwaythePlaylistclassifrequired.ItalsoentailsthatanewPlaylistobjectiscreatedeverytimeweinstantiateaMusicPlayerComponent.Thismightbenotdesiredincertainscenarios,especiallyifweexpectasingletontobeusedacrosstheapplicationandthuskeeptrackoftheplaylist'sstate.
Dependencyinjectionsystemstrytosolvetheseissuesbyproposingseveralpatterns,andthe
www.EBooksWorld.ir
constructorinjectionpatternistheoneenforcedbyAngular2.Thepreviouspieceofcodecouldberethoughtlikethis:
@Component({
selector:'music-player',
templateUrl:'./music-player.component.html'
})
classMusicPlayerComponent{
playlist:Playlist;
constructor(playlist:Playlist){
this.playlist=playlist;
}
}
Now,thePlaylistisinstantiatedoutsideourcomponent.Ontheotherhand,theMusicPlayerComponentexpectssuchanobjecttobealreadyavailablebeforethecomponentisinstantiatedsoitcanbeinjectedthroughitsconstructor.Thisapproachgivesustheopportunitytooverrideitormockitupifwewish.
Basically,thisishowdependencyinjection,andmorespecificallytheconstructorinjectionpattern,works.However,whathasthistodowithAngular2?DoesAngular'sdependencyinjectionmachineryworkbyinstantiatingtypesbyhandandinjectingthemthroughtheconstructor?Obviouslynot,mostlybecausewedonotinstantiatecomponentsbyhandeither(exceptwhenwritingunittests).Angularfeaturesitsowndependencyinjectionframework,whichcanbeusedasastandaloneframeworkbyotherapplications,bytheway.
Theframeworkoffersanactualinjectorthatcanintrospectthetokensusedtoannotatetheparametersintheconstructorandreturnasingletoninstanceofthetyperepresentedbyeachdependency,sowecanuseitstraightawayintheimplementationofourclass,asinthepreviousexample.Theinjectorignoreshowtocreateaninstanceofeachdependency,soitreliesonthelistofprovidersregistereduponbootstrappingtheapplication.Eachoneofthoseprovidersactuallyprovidesmappingsoverthetypesmarkedasapplicationdependencies.Wheneveranentity(let'ssayacomponent,adirective,oraservice)definesatokeninitsconstructor,theinjectorsearchesforatypematchingthattokeninthepoolofregisteredprovidersforthatcomponent.Ifnomatchisfound,itwillthendelegatethesearchontheparentcomponent'sprovider,andwillkeepconductingtheprovider'slookupupwardsuntilaproviderresolveswithamatchingtypeorthetopcomponentisreached.Shouldtheproviderlookupfinishwithnomatch,Angular2willthrowanexception.
Note
Thelatterisnotexactlytrue,sincewecanmarkdependenciesintheconstructorwiththe@Optionalparameterdecorator,inwhichcaseAngular2willnotthrowanyexceptionandthedependencyparameterwillbeinjectedasnullifnoproviderisfound.Thetopmostcomponentisnotthelastdead-endoftheproviderlookup,sincewecanalsodeclareglobaldependenciesinthebootstrap()function,aswewillseelaterinthischapter.
www.EBooksWorld.ir
Wheneveraproviderresolveswithatypematchingthattoken,itwillreturnsuchtypeasasingleton,whichwillbethereforeinjectedbytheinjectorasadependency.Infairness,theproviderisnotjustacollectionofkey/valuepairscouplingtokenswithpreviouslyregisteredtypes,butafactorythatinstantiatesthesetypesandalsoinstantiateseachdependency'sveryowndependenciesaswell,inasortofrecursivedependencyinstantiation.
So,insteadofinstantiatingthePlaylistobjectmanually,wecoulddothis:
import{Component}from'@angular/core';
import{Playlist}from'./playlist';
@Component({
selector:'music-player',
templateUrl:'./music-player.component.html',
providers:[Playlist]
})
classMusicPlayerComponent{
constructor(publicplaylist:Playlist){}
}
Theproviderspropertyofthe@Componentdecoratoristheplacewherewecanregisterdependenciesonacomponentlevel.Fromthatmomentonwards,thesetypeswillbeimmediatelyavailableforinjectionattheconstructorofthatcomponentand,aswewillseenext,atitsownchildcomponentsaswell.
www.EBooksWorld.ir
InjectingdependenciesacrossthecomponenttreeWehaveseenthattheproviderlookupisperformedupwardsuntilamatchisfound.Amorevisualexamplemighthelp,solet'sfigureoutthatwehaveamusicappcomponentthathostsinitsdirectivesproperty(andhenceitstemplate)amusiclibrarycomponentwithacollectionofallourdownloadedtuneswhichalsohosts,initsowndirectivespropertyandtemplate,amusicplayercomponentsowecanplaybackanyofthetunesinourlibrary.
.
├──MusicAppComponent()
│└──MusicLibraryComponent()
│└──MusicPlayerComponent()
...
OurmusicplayercomponentrequiresaninstanceofthePlaylistobjectwementionedbefore,sowedeclareitasaconstructorparameter,convenientlyannotatedwiththePlaylisttoken.
.
├──MusicAppComponent()
│└──MusicLibraryComponent()
│└──MusicPlayerComponent(playlist:Playlist)
...
WhentheMusicPlayerComponententityisinstantiated,theAngularDImechanismwillgothroughtheparametersinthecomponentconstructorwithspecialattentiontotheirtypeannotations.Then,itwillcheckifthattypehasbeenregisteredinthecomponent'sproviderpropertyofthecomponentdecoratorconfiguration.Thecodeisasfollows:
@Component({
selector:'music-player',
providers:[Playlist]
})
classMusicPlayerComponent{
constructor(publicplaylist:Playlist){}
}
But,whatifwewanttoreusethePlaylisttypeinothercomponentsthroughoutthesamecomponenttree?MaybethePlaylisttypecontainsfunctionalitiesinitsAPIthatarerequiredbydifferentcomponentsatonceacrosstheapplication.Dowehavetodeclarethetokenintheprovider'spropertyforeachone?Fortunatelynot,sinceAngular2anticipatesthatnecessityandbringstransversaldependencyinjectionthroughthecomponenttree.
Note
Intheprevioussection,wementionedthatcomponentsconductaproviderlookupupwards.Thisisbecauseeachcomponenthasitsownbuilt-ininjector,whichisspecifictoit.Nevertheless,thatinjectorisinrealityachildinstanceoftheparent'scomponentinjector(andsoonsoforth),soitisfairtosaythatanAngular2applicationhasnotasingleinjector,but
www.EBooksWorld.ir
manyinstancesofthesameinjector,sotosay.
WeneedtoextendtheinjectionofthePlaylistobjecttoothercomponentsinthecomponenttreeinaquickandreusablefashion.Knowingbeforehandthatcomponentsperformaproviderlookupstartingfromitselfandthenpassinguptherequesttoitsparentcomponent'sinjectors,wecanthenaddresstheissuebyregisteringtheproviderintheparentcomponent,oreventhetopparentcomponent,sothedependencywillbeavailableforinjectionforeachandeverychildcomponentfoundunderneathit.Inthissense,wecouldregisterthePlaylistobjectstraightatMusicAppComponent,regardlessitmightnotneeditforitsownimplementation:
@Component({
selector:'music-app',
providers:[Playlist],
directives:[MusicLibraryComponent],
template:'<music-library></music-library>'
})
classMusicAppComponent{}
Theimmediatechildcomponentmightnotrequirethedependencyforitsownimplementationeither.SinceithasbeenalreadyregisteredinitsparentMusicAppComponentcomponent,thereisnoneedtoregisteritthereagain.
@Component({
selector:'music-library',
directives:[MusicPlayerComponent],
template:'<music-player></music-player>'
})
classMusicLibraryComponent{}
Wefinallyreachourmusicplayercomponent,butnowitnolongerfeaturesthePlaylisttypeasaregisteredtokeninitsprovidersproperty.Infact,ourcomponentdoesnotfeatureaproviderspropertyatall.Itnolongerrequiresthis,sincethetypehasbeenalreadyregisteredsomewhereabovethecomponent'shierarchy,beingimmediatelyavailableforallchildcomponents,nomatterwheretheyare.
@Component({
selector:'music-player'
})
classMusicPlayerComponent{
constructor(publicplaylist:Playlist){}
}
Now,weseehowdependenciesareinjecteddownthecomponenthierarchyandhowtheproviderlookupisperformedbycomponentsjustbycheckingtheirownregisteredprovidersandbubblinguptherequestupwardsinthecomponenttree.However,whatifwewanttoconstrainsuchinjectionorlookupactions?
Restrictingdependencyinjectiondownthecomponenttree
www.EBooksWorld.ir
Inourpreviousexample,wesawhowthemusicappcomponentregisteredthePlaylisttokeninitsproviderscollection,makingitimmediatelyavailableforallchildcomponents.Sometimes,wemightneedtoconstraintheinjectionofdependenciestoreachonlythosedirectives(andcomponents)thatareimmediatelynexttoaspecificcomponentinthehierarchy.WecandothatbyregisteringthetypetokenintheviewProviderspropertyofthecomponentdecorator,insteadofusingtheproviderspropertywe'veseenalready.Inourpreviousexample,wecanrestrainthedownwardsinjectionofPlaylistonelevelonly:
@Component({
selector:'music-app',
viewProviders:[Playlist],
directives:[MusicLibraryComponent],
template:'<music-library></music-library>'
})
classMusicAppComponent{}
WeareinformingAngular2thatthePlaylistprovidershouldonlybeaccessiblebytheinjectorsofthedirectivesandcomponentslocatedintheMusicAppComponentview,butnotforthechildrenofsuchcomponents.Theuseofthistechniqueisexclusiveofcomponents,sinceonlytheyfeatureviews.
Restrictingproviderlookup
Justlikewecanrestrictdependencyinjection,wecanconstraindependencylookuptotheimmediateupperlevelonly.Todoso,wejustneedtoapplythe@Host()decoratortothosedependencyparameterswhoseproviderlookupwewanttorestrict:
Import{Component,Host}from'@angular/core';
@Component({
selector:'music-player'
})
classMusicPlayerComponent{
constructor(@Host()playlist:Playlist){}
}
Accordingtotheprecedingexample,theMusicPlayerComponentinjectorwilllookupforaPlaylisttypeatitsparentcomponent'sproviderscollection(MusicLibraryComponentinourexample)andwillstopthere,throwinganexceptionbecausePlaylisthasnotbeenreturnedbytheparent'sinjector(unlesswealsodecorateitwiththe@Optional()parameterdecorator).
www.EBooksWorld.ir
OverridingprovidersintheinjectorhierarchyWe'veseensofarhowAngular'sDIframeworkusesthedependencytokentointrospectthetyperequiredandreturnitrightfromanyoftheprovidersetsavailablealongthecomponenthierarchy.However,wemightneedtooverridetheclassinstancecorrespondingtothattokenincertaincaseswhereamorespecializedtypeisrequiredtodothejob.Angularprovidesspecialtoolstooverridetheprovidersorevenimplementfactoriesthatwillreturnaclassinstanceforagiventoken,notnecessarilymatchingtheoriginaltype.
Wewillnotcoveralltheusecasesindetailhere,butlet'slookatasimpleexample.Inourexample,weassumedthatthePlaylistobjectwasmeanttobeavailableacrossthecomponenttreeforuseindifferententitiesoftheapplication.WhatifourMusicAppComponentdirectivehostsanothercomponentwhosechilddirectivesrequireamorespecializedversionofthePlaylistobject?Let'srethinkourexample:
.
├──MusicAppComponent()
│├──MusicChartsComponent()
││└──MusicPlayerComponent()
│└──MusicLibraryComponent()
│└──MusicPlayerComponent()
...
Thisisabitcontrivedexamplebutwilldefinitelyhelpustounderstandthepointofoverridingdependencies.ThePlaylistinstanceobjectisavailablerightfromthetopcomponentdownwards.TheMusicChartsComponentdirectiveisaspecializedcomponentthatcatersonlyformusicfeaturedinthetopseller'schartsandhenceitsplayermustplaybackbighitsonly,regardlessofthefactitusesthesamecomponentasMusicLibraryComponent.Weneedtoensurethateachplayercomponentgetstheproperplaylistobject,andthiscanbedoneattheMusicChartsComponentlevelbyoverridingtheobjectinstancecorrespondingtothePlaylisttoken.Thefollowingexampledepictsthisscenario,leveragingtheuseoftheprovidefunction:
import{Component,provide}from'@angular/core';
import{Playlist}from'./playlist';
import{TopHitsPlaylist}from'./top-hits-playlist';
@Component({
selector:'music-charts',
directives:[MusicPlayerComponent],
template:'<music-player></music-player>',
providers:[provide(Playlist,{useClass:TopHitsPlaylist})]
})
classMusicChartsComponent{}
Theprovide()functioncreatesaprovidermappedtothetokenspecifiedinthefirstargument(Playlistinthisexample)andtheimplementationconfiguredinthesecondargument,whichinthiscasepointstousingtheTopHitsPlaylisttypeasthereferenceclass.
www.EBooksWorld.ir
WecouldrefactortheblockofcodetouseviewProvidersinstead,soweensurethat(ifrequired)thechildentitiesstillreceiveaninstanceofPlaylistinsteadofTopHitsPlaylist.Alternatively,wecangotheextramileanduseafactory,toreturnthespecificobjectinstanceweneeddependingonotherrequirements.ThefollowingexamplewillreturnadifferentobjectinstanceforthePlaylisttokendependingontheevaluationofaBooleanconditionvariable:
@Component({
selector:'music-charts',
directives:[MusicPlayerComponent],
template:'<music-player></music-player>',
providers:[
provide(Playlist,{useFactory:()=>{
if(condition){
returnnewTopHitsPlaylist();
}else{
returnnewPlaylist();
}
}
})
]
})
classMusicChartsComponent{}
Movingoutfromtheprecedingpseudo-codeexample,howcanweprovideabetterlogicflowwhenusingtheuseFactoryfunction?ItturnsoutthatthemethodsignaturecantakeargumentsthatoperateprettymuchthesameasdependenciesdowhenintheconstructorofanygivenAngularentity.WejustneedtopointAngulartothetypeeachargumenttokenoftheuseFactorylambdafunctionhasbydeclaringtheminthedepspropertyasfollows:
@Component({
selector:'music-charts',
directives:[MusicPlayerComponent],
template:'<music-player></music-player>',
providers:[
ConditionalService,
provide(Playlist,{useFactory:(conditionalService)=>{
if(conditionalService.isTopHits){
returnnewTopHitsPlaylist();
}else{
returnnewPlaylist();
}
},
deps:[ConditionalService]
})
]
})
classMusicChartsComponent{}
Intheprecedingexample,weareinjectinganobjectinstanceofanimaginaryConditionalServiceclass,whichexposesaBooleanpropertynamedisTopHitsthatwillinformabouttheplaylisttobeused.Keepinmindthatthesetypeswillhavetoberegisteredas
www.EBooksWorld.ir
well,eitherintheproviderspropertyofthecurrentcomponentoratanyofitsparentcomponents.
www.EBooksWorld.ir
ExtendinginjectorsupporttocustomentitiesDirectivesandcomponentsrequiredependenciestobeintrospected,resolved,andinjected.Otherentitiessuchasserviceclassesoftenrequirequitesuchfunctionalitytoo.Inourexample,ourPlaylistclassmightrelyonadependencyonaHTTPclienttocommunicatewithathirdpartytofetchthesongs.Theactionofinjectingsuchdependencyshouldbeaseasyasdeclaringtheannotateddependenciesintheclassconstructorandhaveaninjectorreadytofetchtheobjectinstancebyinspectingtheclassprovideroranyotherprovideravailablesomewhere.
Itisonlywhenwethinkhardaboutthelatterthatwerealizethereisagapinthisidea:customclassesandservicesdonotbelongtothecomponenttree.Hence,theydonotbenefitfromanythingsuchasabuilt-ininjectororaparentinjector.Wecannotevendeclareaprovidersproperty,sincewedonotdecoratethesetypesofclasswitha@Componentor@Directivedecorator.Let'stakealookatanexample:
classPlaylist{
songs:string[];
constructor(songsService:SongsService){
this.songs=songsService.fetch();
}
}
WemighttrytheaboveinthehopeofhavingAngular2'sDImechanismintrospectingthesongsServiceparameterofthePlaylistclassconstructorwheninstantiatingthisclassinordertoinjectitintoMusicPlayerComponent.Unfortunately,theonlythingwewilleventuallygetisanexceptionlikethis:
CannotresolveallparametersforPlaylist(?).Makesuretheyallhavevalid
typeorannotations.
Thisiskindofmisleading,sinceallconstructorparametersinPlaylisthavebeenproperlyannotated,right?Aswesaidbefore,theAngularDImachineryresolvesdependenciesbyintrospectingthetypesoftheconstructorparameters.Todoso,itneedssomemetadatatobecreatedbeforehand.EachandeveryAngularentityclassdecoratedwithadecoratorfeaturesthismetadataasaby-productofthewayTypeScriptcompilesthedecoratorconfigurationdetails.However,dependenciesthatalsorequireotherdependencieshavenodecoratorwhatsoeverandnometadataisthencreatedforthem.Thiscanbeeasilyfixedthankstothe@Injectable()decorator,whichwillgivevisibilitytotheseserviceclassesfortheDImechanism:
import{Injectable}from'@angular/core';
@Injectable()
classPlaylist{
songs:string[];
constructor(songsService:SongsService){
this.songs=songsService.fetch();
}
www.EBooksWorld.ir
}
Youwillgetusedtointroducingthatdecoratorinyourserviceclasses,sincetheywillquiteoftenrelyonotherdependenciesnotrelatedtothecomponenttreeinordertodeliverthefunctionality.
Tip
Itisactuallyagoodpracticetodecorateallyourserviceclasseswiththe@Injectable()decorator,irrespectiveofwhetheritsconstructorfunctionshavedependenciesornot.Thisway,wepreventerrorsandexceptionsbecauseofskippingthisrequirementoncetheserviceclassgrowsandrequiresmoredependenciesinthefuture.
www.EBooksWorld.ir
Initializingapplicationswithbootstrap()Aswehaveseeninthischapter,thedependencylookupbubblesupuntilthefirstcomponentatthetop.Thisisnotexactlytrue,sincethereisanadditionalstepthattheDImechanismwillcheckon:thebootstrap()function.
Asfarasweknow,weusethebootstrap()functiontokickstartourapplicationbydeclaringinitsfirstargumenttherootcomponentthatinitiatestheapplication'scomponenttree.However,thebootstrapfunctiontakesasecondargumentintheformofaprovidersarray,wherewecanexplicitdependenciesaswell,thatwillbecomeavailablethroughouttheinjectortree.However,thisisabadpracticebecauseitcouplestheavailabilityofanyprovidertotheapplicationitself,constrainingtheencapsulationandreusabilityofourcomponents,moreoverwhenthebootstrapinitializationfunctionisplatform-specific.
Whereshallwedeclareourapplicationdependenciesthen?Alwaysuseourtoprootcomponentinstead.TheprovidersargumentofthebootstrapfunctionshouldonlybeusedwhenweneedtooverrideexistingAngular2providersonanapplicationlevel,leveragingtheprovide()function,forinstance.
Alwayskeepinmindthatwecanhavemultiplerootcomponents,eachoneofthemtheresultofmultipleexecutionsofthebootstrap()functiondeclaringadifferentrootcomponenteachtime.Eachoneoftheserootcomponentswillfeatureitsownsetofinjectorsandservicesingletons,withnorelationshipwhatsoeveramongstthem.
Switchingbetweendevelopmentandproductionmodes
Angular2applicationsarebootstrappedandinitializedbydefaultindevelopmentmode.Inthedevelopmentmode,theAngular2runtimewillthrowwarningmessagesandassertionstothebrowserconsole.Whilethisisquiteusefulfordebuggingourapplication,wedonotwantthosemessagestobedisplayedwhentheapplicationisinproduction.Thegoodnewsisthatthedevelopmentmodecanbedisabledinfavorofthemoresilentproductionmode.Thisactionisusuallyperformedbeforebootstrappingourapplication:
import{bootstrap}from'@angular/platform-browser-dynamic';
import{enableProdMode}from'angular/core';
importAppComponentfrom'./app.component';
enableProdMode();
bootstrap(AppComponent,[]);
EnablingAngular2'sbuilt-inchangedetectionprofiler
WecanalsoaccessadvancedtoolsfromthebrowserconsolebyenablingtheAngularDebugTools.Todoso,justimporttheenableDebugToolsfunction,whichisspecificintothebrowserplatform,andexecuteitassoonasyougetholdofaninstanceofthecomponentyouwanttoprofile.Thecodeisasfollows:
www.EBooksWorld.ir
import{bootstrap}from'@angular/platform-browser-dynamic';
import{enableDebugTools}from'@angular/platform-browser';
import{ComponentRef}from'angular/core';
importAppComponentfrom'./app.component';
//Thebootstrap()functionreturnsapromisewith
//areferencetothebootstrappedcomponent
bootstrap(AppComponent,[]).then((ref:ComponentRef)=>{
enableDebugTools(ref);
});
WhentheenableDebugTools()functionistriggered,youjustneedtofollowthefollowingstepstoaccessthechangedetectionprofiler:
1. Openthebrowserdevtoolsandswitchtotheconsoleview.2. Typeng.profiler.timeChangeDetection({record:true})andthenpressEnter.3. TheAngular2runtimewillexercisechangedetectioninaloopandwillprintthe
averageamountoftimeasingleroundofchangedetectiontakesforthecurrentstateoftheUI.ACPUprofilerecordingwillbeconductedwhilethechangedetectorisexercised.
Hopefully,thedebugtoolswillbefleshedoutwithmorefunctionalitiesinthefuture.Staytunedandrefertotheofficialdocumentation.
www.EBooksWorld.ir
IntroducingthePomodoroAppdirectorystructureInthepreviouschaptersandsectionsinthischapter,wehaveseendifferentapproachesandgoodpracticesforlayingoutAngular2applications.Theseguidelinesencompassedfromnamingconventionstopointersabouthowtoorganizefilesandfolders.Fromthispointonwards,wearegoingtoputallthisknowledgetopracticebyrefactoringallthedifferentinterfaces,components,directives,pipes,andservicesinanactualAngular2architecture,conformingtothemostcommonlyagreedcommunityconventions.
Bytheendofthischapter,wewillhaveafinalapplicationlayoutthatwrapseverythingwehaveseensofarinthefollowingsitearchitecture:
.
├──app/
│├──shared/
││├──assets/←GlobalCSSorimagefilesarestoredhere
││├──directives/
││├──interfaces/
││├──pipes/
││├──services/
││└──shared.ts←facadeforthe'shared'context
│├──tasks/
││├──(tasks-relatedcomponentsanddirectives)
││└──tasks.ts←facadeforthe'tasks'context
│├──timer/
││├──(timer-relatedcomponentsanddirectives)
││└──timer.ts←facadeforthe'timer'context
││
│├──app.component.ts←toprootapplicationcomponent
│└──main.ts←herewebootstrapthetoprootcomponent
│
├──index.html
├──package.json
├──tsconfig.json
└──typings.json
Itiseasytounderstandthewholerationaleoftheproject.Now,wewillputtogetheranapplicationthatfeaturestwomaincontexts:atimerfeatureandataskslistingfeature.Eachfeaturecanencompassadifferentrangeofcomponents,pipes,directives,orservices.Theinnerimplementationofeachfeatureisopaquetotheotherfeaturesorcontexts.Eachfeaturecontextexposesafacadethatexportsthepiecesoffunctionality(thatis,thecomponent,oneormany)thateachcontextdeliverstotheupper-levelcontextorapplication.Alltheotherpiecesoffunctionality(innerservicesordirectives)areconcealedfromtherestoftheapplication.
Itisfairtosaythatitisdifficulttodrawalineinthesanddifferentiatingwhatbelongstoaspecificcontextoranother.Sometimes,webuildpiecesoffunctionality,suchascertaindirectivesorpipes,whichcanbereusedthroughouttheapplication.So,lockingthemdownto
www.EBooksWorld.ir
aspecificcontextdoesnotmakemuchsense.Forthosecases,wedohavethesharedcontext,wherewestoreanycodeunitwhichismeanttobereusableatanapplicationlevel,apartfrommediafilessuchasstylesheetsorbitmapimagesthatarecomponent-agnostic.
Themainapp.component.tsfilecontainsandexportstheapplicationrootcomponent,whichdeclaresandregistersinitsowninjectorthedependenciesrequiredbyitschildcomponents.Asyouknowalready,allAngular2applicationsmusthaveatleastonerootcomponent,initializedbythebootstrap()function.Thisoperationisactuallyperformedinthemain.tsfile,whichisfiredbytheindex.htmlfile.
Definingacomponentoragroupofrelatedcomponentswithinacontextlikethisimprovesreusabilityandencapsulation.Theonlycomponentthatistightlycoupledwiththeapplicationisthetoprootcomponent,whosefunctionalityisusuallyprettylimitedandentailsbasicallyrenderingtheotherchildcomponentsinitstemplatevieworactingasaroutercomponent,aswewillseeinthenextchapters.
ThelastbitofthepuzzleistheJSONfilesthatcontaintheTypeScriptcompiler,typings,andnpmconfiguration.SinceversioningontheAngular2frameworkkeepsevolving,wewillnotlookattheactualcontentofthesefileshere.Youaresupposedtoknowtheirpurpose,butsomespecificssuchasthepeerdependencyversionschangequiteoftensoyoubetterrefertothebook'sGitHubrepositoryforthelatestup-to-dateversionofeachone.Thepackage.jsonfilerequiresaspecialmentionthough.Thereareafewcommonindustryconventionsandpopularseedprojects,liketheoneprovidedbytheAngularofficialsiteitself.Wehaveprovidedseveralnpmcommandstoeasetheoverallinstallationprocessandthedevelopmentendeavor:
1. Gotohttps://github.com/deeleman/angular2-essentialsanddownloadpackage.json,typings.jsonandtsconfig.jsonintoafolderofyourchoice.Werecommendyoutodownloadindex.htmlaswell.
2. Openupyourconsoleinthefolderwhereyousavedtheprecedingfilesandrunnpminstall.Angular2andallitspeerdependenciesandrequiredtypingswillbedownloadedandinstalledintheprojectworkspace.
3. Now,youcanjustrunnpmstartintheconsole,enabletheTypeScriptcompilerinwatchmode,andfirealocalserverpointingtothisprojectfolder.Werecommendyoutocreatetheapplicationfilesbeforeexecutingthiscommandoranexceptionwillbetriggered.
www.EBooksWorld.ir
RefactoringourapplicationtheAngular2wayInthissection,wewillsplitthecodewecreatedinChapters1,3,and4intocodeunits,followingthesingleresponsibilityprinciple.So,donotexpectmanychangesinthecode,apartfromallocatingeachmoduleinitsowndedicatedfile.Thisiswhywewillfocusmoreonhowtosplitthingsratherthanexplainingeachmodule,whosepurposeyoushouldknowalready.Inanyevent,wewilltakeaminutetodiscusschangesifrequired.
Let'sbeginbycreatinginyourworkfolderthesamedirectorystructurewesawintheprevioussection.Wewillpopulateeachfolderwithfilesonthego.
www.EBooksWorld.ir
ThesharedcontextThesharedcontextiswherewestoreanymodulewhosefunctionalityismeanttobeusedbynotonebutmanycontextsatonce,asitisagnostictothosecontextsaswell.Agoodexampleisthepomodorobitmapwe'vebeenusingtodecorateourcomponents,whichshouldbestoredintheapp/shared/assets/imgpath(pleasedosaveitthere,bytheway).
Anothergoodexampleistheinterfacesthatmodeldata,mostlywhentheirschemacanbereusedacrossadifferentcontextoffunctionality.Forinstance,whenwedefinedtheQueuedOnlyPipeinChapter3,ImplementingPropertiesandEventsinOurComponents,weactionedonlyoverthequeuedpropertyofitemsintherecordset.WecanthenseriouslyconsiderimplementingaQueuedinterfacethatwecanuselaterontoprovidetype-checkingformodulesthatfeaturethatproperty.Thiswillmakeourpipesmorereusableandmodel-agnostic.Thecodeisasfollows:
app/shared/interfaces/queuable.ts
interfaceQueueable{
queued:boolean;
}
exportdefaultQueueable;
Tip
Payattentiontothisworkflow:firstwedefinethemodulecorrespondingtothiscodeunit,andthenweexportit,flaggingitasdefaultsowecanimportitbynamefromelsewhere.Interfacesneedtobeexportedthisway,butfortherestofthebookwewillusuallydeclarethemoduleandexportitinthesamestatement.
Withthisinterfaceinplace,wecannowsafelyrefactortheQueuedOnlyPipetomakeitfullyagnosticfromtheTaskinterfacesothatitisfullyreusableonanycontextwherearecordset,featuringitemsimplementingtheQueuedinterface,needstobefiltered,regardlessofwhattheyrepresent.Thecodeisasfollows:
app/shared/pipes/queued-only.pipe.ts
import{Pipe,PipeTransform}from'angular/core';
import{Queueable}from'../shared';
@Pipe({
name:'pomodoroQueuedOnly',
pure:false
})
exportdefaultclassQueuedOnlyPipeimplementsPipeTransform{
transform(
queueableItems:Queueable[],
...args):Queueable[]{
returnqueueableItems.filter((queueableItem:Queueable)=>{
www.EBooksWorld.ir
returnqueueableItem.queued===args[0]
});
}
}
Asyoucansee,eachcodeunitcontainsasinglemodule.ThiscodeunitconformstothenamingconventionssetforAngular2filenames,clearlystatingthemodulenameincamelcase,plusthetypesuffix(.pipeinthiscase).Theimplementationdoesnotchangeeither,apartfromthefactthatwehaveannotatedallqueue-ableitemswiththeQueuabletype,insteadoftheTaskannotationwehadearlier.Now,ourpipecanbereusedwhereveramodelimplementingtheQueuedinterfaceispresent.
However,thereissomethingthatshoulddrawyourattention:we'renotimportingtheQueuableinterfacefromitssourcelocation,butfromafilenamedshared.tslocatedintheupperlevel.Thisisthefacadefileforthesharedcontext,andwewillexposeallpublicsharedmodulesfromthatfilenotonlytotheclientsconsumingthesharedcontextmodules,buttothoseinsidethesharedcontextaswell.Thereisacaseforthis:ifanymodulewithinthesharedcontextchangesitslocation,weneedtoupdatethefacadesothatanyotherelementreferringtothatmodulewithinthesamecontextremainsunaffected,sinceitconsumesitthroughthefacade.Thisisactuallyagoodmomenttostartbeefingupourveryfirstfacadethen:
app/shared/shared.ts
importQueueablefrom'./interfaces/queueable';
export{
Queueable
};
Asyoucansee,facadeshavenobusinesslogicimplementationand,intheirsimplestincarnation,arejustasummarizedblockofimportspubliclyexposedinasingleexport.NowthatwehaveaworkingQueuableinterfaceandafacade,wecancreatetheotherinterfacewewillrequirethroughoutthebook,correspondingtotheTaskentity,alongwiththeotherpipewerequired—bothexposedthroughthefacadeaswell:
app/shared/interfaces/task.ts
import{Queueable}from'../shared';
interfaceTaskextendsQueueable{
name:string;
deadline:Date;
pomodorosRequired:number;
}
exportdefaultTask;
WeimplementaninterfaceontoanotherinterfaceinTypeScriptbyusingextends(insteadof
www.EBooksWorld.ir
implements).Now,fortheFormattedTimePipe:
app/shared/pipes/formatted-time.pipe.ts
import{Pipe,PipeTransform}from'@angular/core';
@Pipe({
name:'pomodoroFormattedTime'
})
exportdefaultclassFormattedTimePipeimplementsPipeTransform{
transform(totalMinutes:number):string{
letminutes:number=totalMinutes%60;
lethours:number=Math.floor(totalMinutes/60);
return`${hours}h:${minutes}m`;
}
}
Obviously,bothmoduleswillbemadeavailablepubliclyfromthefacade,aswedidpreviously.
Servicesinthesharedcontext
Thereisnoruleofthumbforserviceswithregardtowheretheyshouldgo.Someschoolsofthoughtassumethatservicesaremeredataandlogicprovidersand,assuch,shouldbeagnosticofwhatactuallyconsumesthem,irrespectiveofwhetheritisacomponent,directive,oranyotherservice.Servicesbecomethenfirst-classcitizensthatcaneasilybepromotedtotheirownprojectworkspaceforgreaterreusabilityacrossdifferentprojects.Someotherpractitionersprefertobindservicestothefeaturecontexttheybelongto,ifonlyasinglecontextapplies,favoringencapsulation.Ultimately,itwilldependonthelevelofreusabilityversusencapsulationyouaimtoachieveinyourapplication,andwhatentityactuallymakesuseofthedataandlogicofthoseservices.
Webuiltadataserviceinthepreviouschaptertoserveatasksdatasettopopulateourdatatablewith.Aswewillseelaterinthisbook,thedataservicewillbeconsumedbyothercontextsoftheapplication.So,wewillallocateitinthesharedcontext,exposingitthroughthefacadeasusual:
app/shared/services/task.service.ts
import{Injectable}from'@angular/core';
import{Task}from'../shared';
@Injectable()
exportdefaultclassTaskService{
publictaskStore:Task[]=[];
constructor(){
consttasks=[
{
name:"CodeanHTMLTable",
deadline:"Jun232015",
www.EBooksWorld.ir
pomodorosRequired:1
},{
name:"Sketchawireframeforthenewhomepage",
deadline:"Jun242016",
pomodorosRequired:2
},{
name:"StyletablewithBootstrapstyles",
deadline:"Jun252016",
pomodorosRequired:1
},{
name:"ReinforceSEOwithcustomsitemap.xml",
deadline:"Jun262016",
pomodorosRequired:3
}
];
this.taskStore=tasks.map(task=>{
return{
name:task.name,
deadline:newDate(task.deadline),
queued:false,
pomodorosRequired:task.pomodorosRequired
};
});
}
}
PleasepayattentiontohowweimportedtheInjectable()decoratorandimplementeditonourservice.Itdoesnotrequireanydependencyinitsconstructor,soothermodulesdependingonthisservicewillnothaveanyissuesanywaywhendeclaringitinitsconstructors.Thereasonissimple:itisactuallyagoodpracticetoapplythe@Injectable()decoratorinourservicesbydefaulttoensuretheykeepbeinginjectedseamlesslyaslongastheybegindependingonotherproviders,justincaseweforgettodecoratethemthen.
Configuringapplicationsettingsfromacentralservice
Inthepreviouschapters,wehardcodedalotofstuffinourcomponents:labels,pomodorodurations,pluralmappings,andsoon.Sometimes,ourcontextsaremeanttohaveahighlevelofspecificityandandit'sfinetohavethatinformationthere.Atothertimes,wemightrequiremoreflexibilityandamoreconvenientwaytoupdatethesesettingsapplication-wide.Forthisexample,wewillmakeallthel18npipesmappingsandpomodorosettingsavailablefromacentralservicelocatedinthesharedcontextandexposed,asusual,fromtheshared.tsfacade.
app/shared/services/settings.service.ts
import{Injectable}from'@angular/core';
@Injectable()
exportdefaultclassSettingsService{
timerMinutes:number;
labelsMap:any;
pluralsMap:any;
www.EBooksWorld.ir
constructor(){
this.timerMinutes=25;
this.labelsMap={
'timer':{
'start':'StartTimer',
'pause':'PauseTimer',
'resume':'ResumeCountdown',
'other':'Unknown'
}
};
this.pluralsMap={
'tasks':{
'=0':'Nopomodoros',
'=1':'Onepomodoro',
'other':'#pomodoros'
}
}
}
}
Pleasenotehowweexposecontext-agnosticmappingproperties,whichareactuallynamespaced,tobettergroupthedifferentmappingsbycontext.
Itwouldbeperfectlyfinetosplitthisserviceintotwospecificservices,onepercontext,andlocatetheminsidetheirrespectivecontextfolders,atleastwithregardtothel18nmappings.Keepinmindthatdatasuchasthetimedurationperpomodorowillbeusedacrossdifferentcontextsthough,aswewillseelaterinthischapter.
www.EBooksWorld.ir
CreatingafacademoduleincludingacustomprovidersbarrelWithallthelatestchanges,ourshared.tsfacadeshouldlooklikethis:
app/shared/shared.ts
importQueueablefrom'./interfaces/queueable';
importTaskfrom'./interfaces/task';
importFormattedTimePipefrom'./pipes/formatted-time.pipe';
importQueuedOnlyPipefrom'./pipes/queued-only.pipe';
importSettingsServicefrom'./services/settings.service';
importTaskServicefrom'./services/task.service';
export{
Queueable,
Task,
FormattedTimePipe,
QueuedOnlyPipe,
SettingsService,
TaskService
};
Ourfacadeexposesinterfacetypings,pipes,andserviceproviders.Aswewillseewheninjectingourdependenciesgloballyfromtherootcomponent,itisactuallyquitecommonandconvenienttogroupservices(anddirectivesaswell,includingcomponentsinthesamegroup)intogroupingaliastokens,usuallynamedafterthecontextnamefollowedbythe_PROVIDERSsuffix,allinuppercase.Withregardtothefacade,wecouldintroducethisblockofcoderightabovetheexportstatement:
//importstatementsremainunchangedabove
constSHARED_PIPES:any[]=[
FormattedTimePipe,
QueuedOnlyPipe
];
constSHARED_PROVIDERS:any[]=[
SettingsService,
TaskService
];
export{
Queueable,
Task,
FormattedTimePipe,
QueuedOnlyPipe,
SHARED_PIPES,
SettingsService,
www.EBooksWorld.ir
TaskService,
SHARED_PROVIDERS
};
Thisway,wecanregisterallourprovidersthroughasingletokenwhererequired.Thesameappliestodirectivesandcomponents,wheretheruleofthumbistoexportinthecontextfacadeatokenwiththename{CONTEXTNAME}_DIRECTIVES.Thisgivesustheopportunitytoinjectsupportforallrequiredcomponentsanddirectivesfromacontextinanothercomponentbymeansofasingletoken.Ifsuchcontextwindsupexposingmorecomponentsanddirectivesinthefutureorthenamesofitsexistinglogicalmoduleschange,wewillnotneedthentofollowthetrailofmoduletokensregisteredinthedirectivespropertyofourcomponentsthroughouttheapplication.Wewillseeallthisinactionfurtherupinthischapter.
www.EBooksWorld.ir
CreatingourcomponentsWithoursharedcontextsortedout,timehascometocaterwithourothertwocontexts:timerandtasks.Theirnamesareself-descriptiveenoughofthescopeoftheirfunctionalities.Eachcontextfolderwillallocatethecomponent,HTMLviewtemplate,CSS,anddirectivefilesrequiredtodelivertheirfunctionality,plusafacadefilethatexportsthepubliccomponentsofthisfeature.
Thetimercontext
Ourfirstcontextistheonebelongingtothetimerfunctionality,whichhappenstobethesimpleroneaswell.Itcomprisesauniquecomponentwiththecountdowntimerwebuiltinthepreviouschapters:
app/timer/timer-widget.component.ts
import{Component,Input,OnInit}from'@angular/core';
import{SettingsService}from'../shared/shared';
@Component({
selector:'pomodoro-timer-widget',
template:`
<divclass="text-center">
<imgsrc="/app/shared/assets/img/pomodoro.png">
<h1>{{minutes}}:{{seconds|number:'2.0'}}</h1>
<p>
<button(click)="togglePause()"class="btnbtn-danger">
{{buttonLabelKey|i18nSelect:buttonLabelsMap}}
</button>
</p>
</div>`
})
exportdefaultclassTimerWidgetComponent{
minutes:number;
seconds:number;
isPaused:boolean;
buttonLabelKey:string;
buttonLabelsMap:any;
constructor(privatesettingsService:SettingsService){
this.buttonLabelsMap=settingsService.labelsMap.timer;
}
ngOnInit():void{
this.resetPomodoro();
setInterval(()=>this.tick(),1000);
}
resetPomodoro():void{
this.isPaused=true;
this.minutes=this.settingsService.timerMinutes-1;
this.seconds=59;
this.buttonLabelKey='start';
www.EBooksWorld.ir
}
privatetick():void{
if(!this.isPaused){
this.buttonLabelKey='pause';
if(--this.seconds<0){
this.seconds=59;
if(--this.minutes<0){
this.resetPomodoro();
}
}
}
}
togglePause():void{
this.isPaused=!this.isPaused;
if(this.minutes<this.settingsService.timerMinutes||this.seconds<59){
this.buttonLabelKey=this.isPaused?'resume':'pause';
}
}
}
Asyoucansee,theimplementationisprettymuchthesamewesawalreadybackinChapter1,CreatingOurVeryFirstComponentinAngular2,withtheexceptionofinitializingthecomponentattheinitlifecyclestagethroughtheOnInitinterfacehook.Weleveragethel18nSelectpipetobetterhandlethedifferentlabelsrequiredforeachstateofthetimer,consumingthelabelinformationfromtheSettingsServiceprovider,whichisinjectedbythecomponentinjectorthroughanannotatedargumentintheconstructor.Lateroninthischapter,wewillseewheretoregisterthatprovider.Thepomodorodurationinminutesisalsoconsumedfromtheservice,oncethelatterisboundtoaclassfield.
Thecomponentisexportedpubliclythroughafacade,savingitsclientcomponentsfromhavingtoknowtheactualpathandfilenameofthecomponent.Thecodeisasfollows:
app/timer/timer.ts
importTimerWidgetComponentfrom'./timer-widget.component';
constTIMER_DIRECTIVES:any[]=[
TimerWidgetComponent
];
export{
TIMER_DIRECTIVES,
TimerWidgetComponent
};
PleasenotethattheTIMER_DIRECTIVESaliastokenisincludedforourconvenienceshouldthecomponentrangegrowinthefutureandwewishtotakeadvantageofasingleentrypointtoallcomponentsanddirectivespubliclyavailable.
www.EBooksWorld.ir
Thetaskscontext
Thetaskscontextencompassessomemorelogic,sinceitentailstwocomponentsandadirective.Let'sbeginbycreatingthecoreunitrequiredbyTaskTooltipDirective:
app/tasks/task-tooltip.directive.ts
import{Task}from'../shared/shared';
import{Input,Directive,HostListener}from'@angular/core';
@Directive({
selector:'[task]'
})
exportdefaultclassTaskTooltipDirective{
privatedefaultTooltipText:string;
@Input()task:Task;
@Input()taskTooltip:any;
@HostListener('mouseover')
onMouseOver(){
if(!this.defaultTooltipText&&this.taskTooltip){
this.defaultTooltipText=this.taskTooltip.innerText;
}
this.taskTooltip.innerText=this.task.name;
}
@HostListener('mouseout')
onMouseOut(){
if(this.taskTooltip){
this.taskTooltip.innerText=this.defaultTooltipText;
}
}
}
ThedirectivekeepsalltheoriginalfunctionalityinplaceandjustimportstheAngular2coretypesandtask-typingitrequires.Let'slookattheTaskIconsComponentnow:
app/tasks/task-icons.component.ts
import{Component,Input,OnInit}from'@angular/core';
import{Task}from'../shared/shared';
@Component({
selector:'pomodoro-task-icons',
template:`<img*ngFor="leticonoficons"
src="/app/shared/assets/img/pomodoro.png"
width="{{size}}">`
})
exportdefaultclassTaskIconsComponentimplementsOnInit{
@Input()task:Task;
@Input()size:number;
icons:Object[]=[];
ngOnInit(){
this.icons.length=this.task.pomodorosRequired;
www.EBooksWorld.ir
this.icons.fill({name:this.task.name});
}
}
Sofarsogood.Now,let'sjumptoTasksComponent.Thiscomponentwillrequiresomemoreoverhead,sinceitfeaturesexternalHTMLtemplatesandstylesheets,whicharebasicallythesamewealreadybuiltbackinChapter4,EnhancingourComponentswithPipesandDirectives:
app/tasks/tasks.component.css
h3,p{
text-align:center;
}
.table{
margin:auto;
max-width:860px;
}
app/tasks/tasks.component.html
<divclass="containertext-center">
<h3>
{{queuedPomodoros|i18nPlural:queueHeaderMapping}}fortoday
<spanclass="small"*ngIf="queuedPomodoros>0">(Estimatedtime:
{{queuedPomodoros*timerMinutes|pomodoroFormattedTime}})
</span>
</h3>
<p>
<span*ngFor="letqueuedTaskoftasks|pomodoroQueuedOnly:true">
<pomodoro-task-icons
[task]="queuedTask"
[taskTooltip]="tooltip"
size="50">
</pomodoro-task-icons>
</span>
</p>
<p#tooltip[hidden]="queuedPomodoros===0">Mouseoverfordetails</p>
<h4>Tasksbacklog</h4>
<tableclass="table">
<thead>
<tr>
<th>TaskID</th>
<th>Taskname</th>
<th>Deliverby</th>
<th>Pomodoros</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr*ngFor="lettaskoftasks;leti=index">
<thscope="row">{{i}}
<span*ngIf="task.queued"class="labellabel-info">
Queued
www.EBooksWorld.ir
</span>
</th>
<td>{{task.name|slice:0:35}}
<span[hidden]="task.name.length<35">...</span>
</td>
<td>{{task.deadline|date:'fullDate'}}
<span*ngIf="task.deadline<today"class="labellabel-danger">
Due
</span>
</td>
<tdclass="text-center">{{task.pomodorosRequired}}</td>
<td>
<buttontype="button"class="btnbtn-defaultbtn-xs"
[ngSwitch]="task.queued"
(click)="toggleTask(task)">
<template[ngSwitchWhen]="false">
<iclass="glyphiconglyphicon-plus-sign"></i>
Add
</template>
<template[ngSwitchWhen]="true">
<iclass="glyphiconglyphicon-minus-sign"></i>
Remove
</template>
<templatengSwitchDefault>
<iclass="glyphiconglyphicon-plus-sign"></i>
Add
</template>
</button>
</td>
</tr>
</tbody>
</table>
</div>
Pleasetakeamomenttocheckoutthenamingconventionappliedtotheexternalcomponentfiles,whosefilenamematchesthecomponent'sowntoidentifywhichfilebelongstowhatinflatstructuresinsideacontextfolder.Also,pleasenotehowweremovedthemainpomodorobitmapfromthetemplateandreplacedthehardcodedpomodorotimedurationswithavariablenamedtimerMinutesinthebindingexpressionthatcomputesthetimeestimationtoaccomplishallqueuedtasks.Wewillseehowthatvariableispopulatedinthefollowingcomponentclass:
app/tasks/tasks.component.ts
import{Component,OnInit}from'@angular/core';
importTaskIconsComponentfrom'./task-icons.component';
importTaskTooltipDirectivefrom'./task-tooltip.directive';
import{
TaskService,
SettingsService,
Task,
SHARED_PIPES
}from'../shared/shared';
www.EBooksWorld.ir
@Component({
selector:'pomodoro-tasks',
directives:[TaskIconsComponent,TaskTooltipDirective],
pipes:[SHARED_PIPES],
styleUrls:['app/tasks/tasks.component.css'],
templateUrl:'app/tasks/tasks.component.html'
})
exportdefaultclassTasksComponentimplementsOnInit{
today:Date;
tasks:Task[];
queuedPomodoros:number;
queueHeaderMapping:any;
timerMinutes:number;
constructor(
privatetaskService:TaskService,
privatesettingsService:SettingsService){
this.tasks=this.taskService.taskStore;
this.today=newDate();
this.queueHeaderMapping=settingsService.pluralsMap.tasks;
this.timerMinutes=settingsService.timerMinutes;
}
ngOnInit():void{
this.updateQueuedPomodoros();
}
toggleTask(task:Task):void{
task.queued=!task.queued;
this.updateQueuedPomodoros();
}
privateupdateQueuedPomodoros():void{
this.queuedPomodoros=this.tasks
.filter((Task:Task)=>Task.queued)
.reduce((pomodoros:number,queuedTask:Task)=>{
returnpomodoros+queuedTask.pomodorosRequired;
},0);
}
};
SeveralaspectsoftheTasksComponentimplementationareworthhighlighting:
Weimportthecomponent'srequiredchildcomponentanddirectivesrelatively.Ifwetriedtobringthemfromthefacade,wewouldgetanundefinedvaluebecauseofacircularreference.Wedonotimportalltherequiredpipes,justitsSHARED_PIPESaliastoken,registeringitinthepipe'scomponentdecoratorproperty.WeinjecttheTaskServiceandSettingsServiceprovidersinthecomponent,leveragingAngular'sDIsystem.Thedependenciesareinjectedwithaccessorsrightfromtheconstructor,becomingprivateclassmembersonthespot.Thetasksdatasetandthepomodorotimedurationarethenpopulatedfromtheboundservices.
www.EBooksWorld.ir
Ourlaststepistoexposethefacaderequiredforthisfeaturecontext.Inallfairness,wearenotmeanttoexporteverythingoneverycontext.Wecansimplygetawaywithexportingonlythemaincontextcomponent,whileleavinganyothersubcomponentordirectiveoutsidethescopeofthecontextfacade.Thatisactuallywhatwewilldoinhere,sinceitisquiteunlikelythatanyhostcomponentwouldeverneedtoincludeinitsownviewtheTaskIconsComponent.WewillneverthelessexporttheTaskTooltipDirective,sinceitsimplementationmightbereusedinthefuturebysomeothercomponentdealingwiththe[task]inputproperties.
app/tasks/tasks.ts
importTasksComponentfrom'./tasks.component';
importTaskTooltipDirectivefrom'./task-tooltip.directive';
constTASKS_DIRECTIVES:any[]=[
TasksComponent,
TaskTooltipDirective
];
export{
TASKS_DIRECTIVES,
TasksComponent,
TaskTooltipDirective
};
Pleasecheckhowweconformtothenamingconventionforaliastokenswhengroupingcomponentsanddirectivesintheirownaliastoken.
Definingthetoprootcomponent
Withallourfeaturecontextsready,timehascometodefinethetoprootcomponent,whichwillkickstartthewholeapplicationasaclusterofcomponentslaidoutinatreehierarchy.Therootcomponentusuallyhasaminimumimplementation.Basically,itsgoalistoregisterthedependencyproviderstheapplicationwillrequireassingletonsatdifferentlevelsofthecomponenthierarchy,andinstantiateinitsviewtemplatethemainchildcomponentsthatwilleventuallyevolveintobranchesofchildcomponents.
app/app.component.ts
import{Component}from'@angular/core';
import{TIMER_DIRECTIVES}from'./timer/timer';
import{TASKS_DIRECTIVES}from'./tasks/tasks';
import{SHARED_PROVIDERS}from'./shared/shared';
@Component({
selector:'pomodoro-app',
directives:[TIMER_DIRECTIVES,TASKS_DIRECTIVES],
providers:[SHARED_PROVIDERS],
template:`
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
www.EBooksWorld.ir
<strongclass="navbar-brand">MyPomodoroApp</strong>
</div>
</div>
</nav>
<pomodoro-timer-widget></pomodoro-timer-widget>
<pomodoro-tasks></pomodoro-tasks>
`
})
exportdefaultclassAppComponent{}
Pleasecheckhowweconvenientlyimportthealiastokensandregisterthemintheprovidersanddirectivespropertiesofthecomponentdecorator.TheSHARED_PROVIDERStokendeservesaspecialmention.Alltheprovidersgroupedbyitarenowavailabledownthetreeofcomponentsthathangsfromthistoprootcomponent,sothere'snoneedtoregisteritagainandthestateofeachproviderwillremainconsistentacrosstheapplicationdomain.
Theonlyexceptiontothiswouldbetohaveonecomponentatsomelevelregistering,forargument'ssake,theTaskServiceasaprovider.Thatwouldturnintoanewinstanceoftheserviceatthatcomponentlevelandforallitschildcomponentsaswell.
www.EBooksWorld.ir
BootstrappingtheapplicationWenowhaveafull-blownapplicationfeaturingdifferentfunctionalitycontexts,wrappedbyatoprootcomponent.Thelaststepofourendeavorwillbetobootstraptheapplication,byimportingthemaintoprootcomponentandpassingitovertothebootstrap()function:
app/main.ts
import{bootstrap}from'@angular/platform-browser-dynamic';
importAppComponentfrom'./app.component';
bootstrap(AppComponent);
Thisfilemustbeimportedfromthemainindex.htmlfileinordertotriggerthewholeprocess,byusingastandardmoduleloadersuchasSystemJSorWebPack.Inthebookrepository,weuseSystemJSsopleaserefertothechaptercodethereforfurtherreference.Intheindex.htmlfile,wewillexpectacustomelementmatchingthetoprootcomponentselector,asshowninthefollowingindex.htmltranscription:
<!DOCTYPEhtml>
<html>
<head>
<metacharset="utf-8">
<title>MyAngular2PomodoroApplication</title>
<scriptsrc="node_modules/es6-shim/es6-shim.min.js"></script>
<scriptsrc="node_modules/zone.js/dist/zone.js"></script>
<scriptsrc="node_modules/reflect-metadata/Reflect.js"></script>
<scriptsrc="node_modules/systemjs/dist/system.js"></script>
<scriptsrc="node_modules/rxjs/bundles/Rx.js"></script>
<scriptsrc="systemjs.config.js"></script>
<script>
System.import('built/app/main')
.then(null,console.error.bind(console));
</script>
<linkrel="stylesheet"
href="node_modules/bootstrap/dist/css/bootstrap.min.css">
<basehref="/">
</head>
<body>
<pomodoro-app>Loading...</pomodoro-app>
</body>
</html>
Withallthefilesinplace,wecansafelycompiletheprojectandseetheresultsservedbyawebserverinabrowserwindow.Ifyouhavedownloadedpackage.json(andrelatedJSONfiles)fromthebookrepo,pleaserunnpmstartfromyourterminalwindowandenjoy.You
www.EBooksWorld.ir
madeafantasticpomodoroapplicationbyyourself!
www.EBooksWorld.ir
SummaryThischapterhasdefinitelysetthefoundationforallthegreatapplicationsthatyouwillbebuildingontopofAngular2fromnowon.TheAngular2dependencymanagementimplementationisinfactoneofthegemsofthisframeworkandatimesaver.Applicationarchitecturesbasedoncomponenttreesarenotrocketscienceanymore,andwehavefollowedthispatterntosomeextentwhilebuildingwebsoftwareinotherframeworkssuchasAngular1.xandReact.
ThischapterconcludesourtripthroughthecoreofAngular2anditsapplicationarchitecture,settingupthestandardsthatwewillfollowfromnowonwhilebuildingapplicationsontopofthisnewandexcitingframework.
Inthenextchapters,wewillfocusonveryspecifictoolsandmodulesthatwecanusetosolveeverydayproblemswhencraftingourwebprojects.WewillseehowtodevelopbetterHTTPnetworkingclientswithAngular2.
www.EBooksWorld.ir
Chapter6.AsynchronousDataServiceswithAngular2ConnectingtodataservicesandAPIsandhandlingasynchronousinformationisacommontaskinoureverydaylifeasdevelopers.Inthissense,Angular2providesanunparalleledtoolsettohelpitsenthusiasticdeveloperswhenitcomestoconsuming,digesting,andtransformingallkindsofdatafetchedfromdataservices.
TherearesomanypossibilitiesthatitwouldrequireanentirebooktodescribeallthatyoucandotoconnecttoAPIsorconsumeinformationfromthefilesystemasynchronouslythroughHTTP.Inthisbook,wewillonlyscratchthesurface,buttheinsightscoveredinthischapterabouttheHTTPAPIanditscompanionclassesandtoolswillgiveyouallthatyouneedtoconnectyourapplicationstoHTTPservicesinnotime,leavingtoyourcreativityallthatyoucandowiththem.
Inthischapter,wewill:
LookatthedifferentstrategiesforhandlingasynchronousdataIntroduceObservablesandObserversDiscussfunctionalreactiveprogrammingandRxJSReviewtheHTTPclassanditsAPI,aspartoftheCovertheHTTP_PROVIDERSmoduleSeealloftheprecedingpointsinactionthroughactualcodeexamples
www.EBooksWorld.ir
StrategiesforhandlingasynchronousinformationConsuminginformationfromanAPIisacommonoperationinourdailypractice.WeconsumeinformationoverHTTPallthetime—whenauthenticatingusersbysendingoutcredentialstoanauthenticationservice,orwhenfetchingthelatesttweetsinourfavoriteTwitterwidget.Modernmobiledeviceshaveintroducedanunparalleledwayofconsumingremoteservices,deferringtherequestsandresponseconsumptionuntilmobileconnectivityisavailable.Responsivityandavailabilityhavebecomebigdeals.AndalthoughmodernInternetconnectionsareultra-fast,thereisalwaysaresponsetimeinvolvedwhenservingsuchinformationthatforcesustoputinplacemechanismstohandlestateinourapplicationsinatransparentwayfortheenduser.
Thisisnotspecifictoscenarioswhereweneedtoconsumeinformationfromanexternalresource.Sometimes,wemightneedtobuildfunctionalitiesthatdependontimeasaparameterofsomething,andweneedtointroducecodepatternsthathandlethisdeferredchangeintheapplicationstate.
Forallthesescenarios,wehavealwaysusedcodepatterns,suchasthecallbackpattern,wherethefunctionthattriggerstheasynchronousactionexpectsanotherfunctioninitssignature,whichwillemitasortofnotificationassoonastheasynchronousoperationiscompleted:
functionnotifyCompletion(){
console.log('Ourasynchronousoperationhasbeencompleted');
}
functionasynchronousOperation(callback){
setTimeout(callback,5000);
}
asynchronousOperation(notifyCompletion);
Theproblemwiththispatternisthatcodecanbecomequiteconfusingandcumbersomeastheapplicationgrowsandmoreandmorenestedcallbacksareintroduced.Inordertoavoidthisscenario,Promisesintroducedanewwayofenvisioningasynchronousdatamanagementbyconformingtoaneaterandmoresolidinterface,inwhichdifferentasynchronousoperationscanbechainedatthesamelevelandevenbesplitandreturnedfromotherfunctions:
functionnotifyCompletion(){
console.log('Ourasynchronousoperationhasbeencompleted');
}
functionasynchronousOperation(){
varpromise=newPromise((resolve,reject)=>{
setTimeout(resolve,5000);
});
returnpromise;
www.EBooksWorld.ir
}
asynchronousOperation().then(notifyCompletion);
Theprecedingcodeexampleisperhapsabitmoreverbose,butitdefinitelyproducesamoreexpressiveandelegantinterfaceforourasynchronousOperationfunction.
So,Promisestookoverthecodingarenabystormandnodeveloperoutthereseemstoquestionthegreatvaluetheybringtothegame.Sowhydoweneedanotherparadigm?Well,becausesometimeswemightneedtoproducearesponseoutputthatfollowsamorecomplexdigestprocessasitisbeingreturned,orevencancelthewholeprocess.ThiscannotbedonewithPromises,becausetheyaretriggeredassoonasthey'reinstanced.Inotherwords,Promisesarenotlazy.Ontheotherhand,thepossibilityoftearingdownanasynchronousoperationafterithasbeenfiredbutnotcompletedyetcanbecomequitehandyincertainscenarios.Promisesonlyallowustoresolveorrejectanasynchronousoperation,butsometimeswemightwanttoaborteverythingbeforegettingtothatpoint.Ontopofthat,promisesbehaveasaone-timeoperation.Oncetheyareresolved,wecannotexpecttoreceiveanyfurtherinformationorstatechangenotificationunlesswereruneverythingfromscratch.Moreover,wesometimesneedamoreproactiveimplementationofasyncdatahandling.ThisiswhereObservablescomeintothegame.
www.EBooksWorld.ir
ObservablesinanutshellAnObservableisbasicallyanasynceventemitterthatinformsanotherelement,calledtheObserver,thestatehaschanged.Inordertodoso,theObservableimplementsallofthemachinerythatitneedstoproduceandemitsuchasyncevents,anditcanbefiredandcanceledatanytimeregardlessofwhetherithasemittedtheexpecteddataeventsalreadyornot.
Thispatternallowsconcurrentoperationsandmoreadvancedlogic,sincetheObserversthatsubscribetotheObservableasynceventswillreacttoreflectthestatechangeoftheObservabletheysubscribeto.
Thesesubscribers,whicharetheObserverswementionedearlier,willkeeplisteningtowhateverhappensintheObservableuntiltheObservableisdisposed,ifthateverhappenseventually.Inthemeantime,informationwillbeupdatedthroughouttheapplicationwithnointerventionwhatsoeveroftriggeringroutines.
Wecanprobablyseeallthiswithmoretransparencyinanactualexample.Let'srefactortheexamplewecoveredwhenassessingpromise-basedasyncoperationsandreplacethesetTimeoutcommandwithsetInterval:
functionnotifyCompletion(){
console.log('Ourasynchronousoperationhasbeencompleted');
}
functionasynchronousOperation(){
varpromise=newPromise((resolve,reject)=>{
setInterval(resolve,2000);
});
returnpromise;
}
asynchronousOperation().then(notifyCompletion);
Copyandpastetheprecedingsnippetinyourbrowser'sconsolewindowandseewhathappens:thetextOurasynchronousoperationhasbeencompletedwillshowupatthedevtools'consoleonlyonceafter2secondsandwillneverberenderedagain.Thepromiseresolveditselfandtheentireasynceventwasterminatedatthatverymoment.
Now,pointyourbrowsertoanonlineJavaScriptcodeplaygroundsuchasJSBIN(https://jsbin.com/),andcreateanewcodesnippetenablingjusttheJavaScriptandtheConsoletabs.Then,makesureyouaddtheRxJSlibraryfromtheAddlibraryoptiondropdown(wewillneedthislibrarytocreateobservables,butdon'tpanic;wewillcoverthislaterinthechapter)andinsertthefollowingcodesnippet:
varobservable=Rx.Observable.create(observer=>{
setInterval(()=>{
observer.onNext('Myasyncoperation');
},2000);
});
www.EBooksWorld.ir
observable.subscribe(response=>console.log(response));
Runitandexpectsomemessagetoappearontherightpane.2secondslater,wewillseethesamemessageshowingup,andthenagainandagain.Inthissimpleexample,wecreatedanobservableandthensubscribedtoitschanges,throwingtotheconsolewhateveritemits(asimplemessageinthisexample)asasortofpushnotification.
TheObservablereturnsastreamofeventsandoursubscribersreceivepromptnotificationofthosestreamedevents,actingaccordingly.ThisiswhatthemagicofObservablesrelieson—Observablesdonotperformanasyncoperationanddie(althoughwecanconfigurethemtodoso),butstartastreamofcontinuouseventswecansubscribeoursubscribersto.
Ifwecommentoutthelastline,nothingwillhappen.TheConsolepanewillremainsilentandallthemagicwillbeginonlywhenwesubscribeoursourceobject.
That'snotall,however.ThisstreamcanbethesubjectofmanyoperationsbeforehittingtheObserverssubscribedtothem.Justaswecangrabacollectionobject,suchasanarray,andapplyfunctionalmethodsoveritsuchasmap()orfilter()inordertotransformandplayaroundwiththearrayitems,wecandothesamewiththestreamofeventsthatareemittedbyourObservables.Thisiswhatisknownasreactivefunctionalprogramming,andAngular2makesthemostofthisparadigmtohandleasynchronousinformation.
www.EBooksWorld.ir
ReactivefunctionalprogramminginAngular2TheObservablepatternstandsatthecoreofwhatweknowasreactivefunctionalprogramming.Basically,themostbasicimplementationofareactivefunctionalscriptencompassesseveralconceptsthatweneedtobecomefamiliarwith:
AnObservableAnObserverAtimelineAstreamofeventsfeaturingthesamebehaviorasanobjectscollectionAsetofcomposableoperators,alsoknownasReactiveExtensions
Soundsdaunting?It'snot.Believeuswhenwetellyouthatallofthecodeyouhavegonethroughsofarismuchmorecomplexthanthis.Thebigchallengehereistochangeyourmindsetandlearntothinkinareactivefashion,andthatisthemaingoalofthissection.
Ifwewanttoputitsimply,wecanjustsaythatreactiveprogrammingentailsapplyingasynchronoussubscriptionsandtransformationstoObservablestreamsofevents.Wecanimagineyourpokerfacenow,solet'sputtogetheramoredescriptiveexample.
Thinkaboutaninteractiondevicesuchasakeyboard.Akeyboardhaskeysthattheuserpresses.Eachoneofthosekeystrokestriggersakeypressevent.Thatkeypresseventfeaturesawiderangeofmetadata,including—butnotlimitedto—thenumericcodeofthespecifickeytheuserpressedatagivenmoment.Astheusercontinueshittingkeys,morekeyUpeventsaretriggeredandpipedthroughanimaginarytimeline.Congratulations!YouhaveanObservablesequenceintheworkshere:thekeyboardassumestheroleofanObservableandemitsasequenceofeventsthatinformofthekeyspressednomatterwhathappensnext.Eachkeystrokeeventisagnosticfromtherestandtheyfollowasequencealongtime.Nowwementionevents,oneoccurringafteranother.Ifwegrabagroupofelementsofagiventype(notnecessarilythesametypethough)andwraptheminanobject(whichiswhatthattimelineis),isn'titacollection?Moreover,thatcollectionisspannedalongtime.Soisn'tthatastream?Indeeditis,socongratulations!YounowhaveaneventstreamemittedbytheObservableforyourlisteningdelight:
www.EBooksWorld.ir
Ourstreamofkeypresseventsislookingreallygood,butwearegettingnotificationsforallthekeyspressed.Whatifwewanttosubscribetoasubsetofthestreamthatobservestheeventsrepresentedbycursorkeys?Wecouldbecodingasimplegameandthosearethekeysthatcontrolouravatarinthegame,whileotherkeyeventsarecompletelyuselessforthepurposeofthegame.Ouridealstreamshouldcontainonlycursorkeys'eventsthen.Thisiswherethefunctionalpartofthereactiveparadigmcomesintoplay.
Let'simaginewearegrabbingthateventstreamandfilteringittoincludeonlythekeypresseventsthatpertaintointeractionswiththecursorkeys,disregardingalltheothers.Ontopofthat,wearegoingtothrottlethestreamtoallowakeypresseventtopassthroughonlyevery500milliseconds,sowedonotoverflowwhateverislisteningattheotherendofthewire.Nowwehaveaneweventstream,whichistheby-productofthepreviousone,butrepresentsonlyasubsetoftheinformationwithaveryspecificgoal:
www.EBooksWorld.ir
Ifwesubscribetothislaststream,wecantakeactiononeachcursorkeypressandapplysomegamificationlogictowhatevergamewearedevelopingatthistime.Wecanevenhookupseveralsubscriberstothatstream,orapplyfurthertransformationsusingotherReactiveExtensionstocreatebrandnewstreamsthatothersubscriberscansubscribetoinordertoperformotherbusinessorpresentationlogicnotrelatedwhatsoevertothatperformedbytheoriginalsubscriber.
Thislogiccanbeportedtotherealmofcomponentstohandleinteractionevents,asynchronousbehavior,or(asthischapteraimstodescribe)theconsumptionanddigestionofinformationservedbyanAPIserviceordatastore.Inordertodoso,Angular2reliesonRxJS,whosecoremoduleisrequiredasapeerdependencywheninstallingAngular2.TheRxJSAPIprovidesallthatweneedtoputtheaforementionedthingstoworkonourasynchronousoperationswithAngular2.
www.EBooksWorld.ir
TheRxJSlibraryAsmentionedpreviously,Angular2comeswithapeerdependencyonRxJS,theJavaScriptflavoroftheReactiveXlibrarythatallowsustocreateObservablesandObservablesequencesoutofalargevarietyofscenarios,suchasinteractionevents,promises,orcallbackfunctions,justtonameafew.Inthatsense,reactiveprogrammingdoesnotaimtoreplaceasynchronouspatternssuchaspromisesorcallbacks.Allthewayaround,itcanleveragethemaswelltocreateObservablesequences.
RxJScomeswithbuilt-insupportforawiderangeofcomposableoperatorstotransform,filter,andcombinetheresultingeventstreams.ItsAPIprovidesconvenientmethodstosubscribeObserverstothesestreamssothatourscriptsandcomponentscanrespondaccordinglytostatechangesorinteractioninputs.WhileitsAPIissomassivethatcoveringitindetailisoutofthescopeofthisbook,wewillhighlightsomebitsofitsmostbasicimplementationinorderforyoutobetterunderstandhowHTTPconnectionsarehandledbyAngular2.
BeforejumpingontotheHTTPAPIprovidedbyAngular2,let'screateasimpleexampleofanObservableeventstreamthatwecantransformwithReactiveExtensionsandsubscribeobserversto.Todoso,let'spickthescenariodescribedintheprevioussection.
Weenvisionedhowauserinteractingwithourapplicationthroughthekeyboardcan'tturnintoatimelineofkeystrokesand,therefore,aneventstream.GobacktoJSBIN,deletethecontentsoftheJavaScriptpane,andthenwritedownthefollowingsnippet:
varkeyboardStream=Rx.Observable
.fromEvent(document,'keyup')
.map(x=>x.which);
Theprecedingcodeisprettyself-descriptive.WeleveragetheRx.ObservableclassanditsfromEventmethodtocreateaneventemitterthatstreamsthekeyupeventsthattakeplaceinthescopeofthedocumentobject.Eachoftheeventobjectsemittedisacomplexobject,sowesimplifythestreamedobjectsbymappingtheeventstreamontoanewstreamthatcontainsonlythekeycodespertainingtoeachkeystroke.ThemapmethodisaReactiveExtensionthatfeaturesthesamebehaviorastheJavaScriptmapfunctionalmethod.Thisiswhyweusuallyrefertothiscodestyleasreactivefunctionalprogramming.
Allright,sonowwehaveaneventstreamofnumerickeystrokes,butweareonlyinterestedinobservingthoseeventsthatinformofhitsonthecursorkeys.WecanbuildanewstreamoutofanexistingstreambyapplyingmoreReactiveExtensions.So,let'sdoitwithkeyboardStreambyfilteringsuchastreamandreturningonlythoseeventsthatarerelatedtocursorkeys.Wewillalsomapthoseeventstotheirtextcorrespondenceforthesakeofclarity.Appendthefollowingchunkofcoderightbelowtheprevioussnippet:
varcursorMovesStream=keyboardStream.filter(x=>{
returnx>36&&x<41;
www.EBooksWorld.ir
})
.map(x=>{
vardirection;
switch(x){
case37:
direction='left';
break;
case38:
direction='up';
break;
case39:
direction='right';
break;
default:
direction='down';
}
returndirection;
});
WecouldhavedoneallofthisinasingleactionbychainingthefilterandmapmethodstothekeyboardStreamObservableandthensubscribingtoitsoutput,butit'sgenerallyagoodideatoseparateconcerns.Byshapingourcodeinthisway,wehaveagenerickeyboardeventsstreamthatwecanreuselateronforsomethingcompletelydifferent.So,ourapplicationcanscaleupwhilekeepingthecodefootprinttoaminimum.
Nowthatwehavementionedsubscribers,let'ssubscribetoourcursormovesstreamandthrowthemovecommandsattheconsole.Wetypethefollowingstatementattheendofourscript,thencleartheConsolepane,andclickontheOutputtabsothatwecanhaveadocumentavailable:
cursorMovesStream.subscribe(e=>console.log(e));
ClickanywhereontheOutputpanetoputthefocusonitandstarttypingrandomkeyboardkeysandcursorkeys:
www.EBooksWorld.ir
YouareprobablywonderinghowwecanapplythispatterntoanasynchronousscenariosuchasconsuminginformationfromaHTTPservice.Basically,youhavesofarbecomeusedtosubmittingasyncrequeststoAJAXservicesandthendelegatingtheresponsehandlingtoacallbackfunctionorjustpipingitthroughapromise.Now,wewillhandlethecallbyreturninganObservable.ThisObservablewillemittheserverresponseasaneventinthecontextofastream,whichwillbefunneledthroughReactiveExtensionstobetterdigesttheresponse.
www.EBooksWorld.ir
IntroducingtheHTTPAPITheHttpclassprovidesapowerfulAPIthatabstractsalltheoperationsrequiredtohandleasynchronousconnectionsthroughavarietyofHTTPmethods,handlingtheresponsesinaneasyandcomfortableway.ItsimplementationhasbeenmadewithalotofcaretoensurethatprogrammerswillfeelateasewhiledevelopingsolutionsthattakeadvantageofthisclasstoconnecttoanAPIoradataresource.
Inanutshell,instancesoftheHttpclass(whichhasbeenimplementedasanInjectableresourceandcanthereforebeusedinourclassesconstructorsjustbyinjectingitasadependencyprovider)exposeaconnectionmethodnamedrequest()toperformanytypeofhttpconnection.TheAngular2teamhascreatedsomesyntaxshortcutsforthemostcommonrequestoperations,suchasGET,POST,PUT,andeveryexistingHTTPverb.So,creatinganasyncHttprequestisaseasyasthis:
varrequestOptions=newRequestOptions({
method:RequestMethod.Get,
url:'/my-api/my-data-store.json'
});
varrequest=newRequest(requestOptions);
varmyHttpRequest:Observable<Response>=http.request(request);
Also,allofthiscanbesimplifiedintoasinglelineofcode:
varmyHttpRequest:Observable<Response>=http.get('/my-api/my-data-store.json');
Aswecansee,theHttpclassconnectionmethodsoperatebyreturninganObservablestreamofResponseobjectinstances.ThisallowsustomapanddigesttheoutputoftheHttpcallassoonasitisavailableandsubscribeObserverstothestream,whichwillprocesstheinformationaccordinglyonceitisreturned,asmanytimesasrequired:
myHttpRequest.map(response:Response=>response.json())
.subscribe(data=>console.log(data));
Intheprecedingexample,wemaptheresponsesemittedbytheObservableeventstream(aswecansee,thosearebasicallyinstancesoftheResponseclassprovidedbyAngular2)toanewcollectionofeventsrepresentingJSONinstancesofeachresponse.Thisisdonebyleveragingthejson()methodoftheResponseclass,whichwewillcoverlateroninthischapter.ThenwesubscribetotheresultingstreamandthrowtheoutputoftheHTTPcalltotheConsoleonceourObserverreceivesthedatanotification.
Bydoingthis,wecanrespawntheHttprequestasmanytimesasweneed,andtherestofourmachinerywillreactaccordingly.WecanevenmergetheeventstreamrepresentedbytheHttpcallwithotherrelatedcalls,andcomposemorecomplexObservablestreamsanddatathreads.Thepossibilitiesareendless.
www.EBooksWorld.ir
WhentousetheRequestandRequestOptionsArgsclassesWementionedtheRequestandRequestOptionsclasseswhileintroducingtheHttpclassatfirst.Onaregularbasis,youwillnotneedtomakeuseoftheselow-levelclasses,mostlybecausetheshortcutmethodsprovidedbytheHttpclassabstracttheneedtodeclaretheHTTPverbinuse(GET,POST,andsoon)andtheURLwewanttoconsume.Withthisbeingsaid,youwillsometimeswanttointroducespecialHTTPheadersinyourrequestsorappendquerystringparametersautomaticallytoeachrequest,forargument'ssake.Thatiswhytheseclassescanbecomequitehandyincertainscenarios.ThinkofausecasewhereyouwanttoaddanauthenticationtokentoeachrequestinordertopreventunauthorizedusersfromreadingdatafromoneofyourAPIendpoints.
Inthefollowingexample,wereadanauthenticationtokenandappenditasaheadertoourrequesttoadataservice.Contrarytoourexample,wewillinjecttheoptionshashobjectstraightintotheRequestconstructor,skippingthestepofcreatingaRequestOptionsobjectinstance.Angular2providesawrapperclassfordefiningcustomheadersaswell,andwewilltakeadvantageofitinthisscenario.Let'sfigureoutthatwedohaveanAPIthatexpectsallrequeststoincludeacustomheadernamedAuthorization,attachingtheauthTokenthatwereceivedwhenloggingintothesystem,whichwasthenpersistedinthebrowser'slocalstoragelayer,forinstance:
varauthToken=window.localStorage.getItem('auth_token');
varheaders=newHeaders();
headers.append('Authorization',`Token${authToken}`);
varrequest=newRequest({
method:RequestMethod.Get,
url:'/my-api/my-secured-data-store.json',
headers:headers
});
varauthRequest:Observable<Response>=http.request(request);
Again,wewouldliketonotethatapartfromthisscenario,youwillseldomneedtocreatecustomrequestconfigurations,unlessyouwanttodelegatethecreationofrequestconfigurationsinafactoryclassormethodandreusethesameHttpwrapperallthetime.Angular2givesyoualltheflexibilitytogoasfarasyouwishwhenabstractingyourapplications.
www.EBooksWorld.ir
TheResponseobjectAswehavealreadyseen,theHTTPrequestsperformedbytheHttpclassreturnanobservablestreamofResponseclassinstances.SimilartotheRequestobject,youwillrarelyfindyourselfinneedofinstantiatingthisclass.However,understandingtheResponseclassinterfaceisquiteusefulinordertounderstandthestatusofourrequest,handleconnectionerrors,andproperlydigesttheinformationreturnedinthestream.
Inourfirstexample,wemappedthecontentofthestreamasastreamofJSONobjectsbyexecutingthejson()method.ThismethodparsestheresponsebodyasaJSONobject,orraisesanexceptionifsuchabodycannotbeparsed.Besidesthismethod,theResponseclassexposesthetext()method,whichwillparseandreturntheresponsebodyasaplainstring.
Let'sfigurethisout:wehaveacomponentthatexposesastringfieldnamedbio,whichisrenderedinthecomponent'stemplate.WemightwanttoservethisbiopropertyfromaRESTAPI.Hence,wecouldimplementsuchfunctionalityinafewlinesofcode,likethis:
http.get('/api/bio')
.map(res:Response=>res.txt)
.subscribe(bio=>this.bio=bio);
TheResponseobjectexposesotherminormethodsandsomeinterestingproperties,suchasthenumericstatusproperty,whichinformsofthestatuscodereturnedbytheserver.ThebytesLoadedandtotalBytesnumericpropertiesbecomequiteusefulwhenscaffoldingpreloadnotifiersinprogressevents.Specialmentiongoestotheheadersproperty,whichreturnsanobjectbasedontheHeadersclass(https://fetch.spec.whatwg.org/#headers-class)oftheFetchAPI.
CoverageforallthemethodsandpropertiesavailableintheResponseclassAPIisdefinitelybeyondthescopeofthisbook,asyouwillnotbeusingthoseonaregularbasis,butweencourageyoutoexpandyourknowledgeonthesubjectbyvisitingtheofficialdocumentation(https://angular.io/docs).
www.EBooksWorld.ir
HandlingerrorswhenperformingHttprequestsHandlingerrorsraisedinourrequestsbyinspectingtheinformationreturnedintheResponseobjectisactuallyquitesimple.WejustneedtoinspectthevalueofitsBooleanproperty,whichwillreturnfalseiftheHTTPstatusoftheresponsefallssomewhereoutsideofthe2xxrange,clearlyindicatingthatourrequestcouldnotbeaccomplishedsuccessfully.Wecandouble-checkthatbyinspectingthestatuspropertytounderstandtheerrorcodeorthetypeproperty,whichcanassumethefollowingvalues:basic,cors,default,error,oropaque.InspectingtheresponseheadersandthestatusTextpropertyoftheResponseobjectwillprovideinsightfulinformationabouttheoriginoftheerror.
Allinall,wearenotmeanttoinspectthosepropertiesoneveryresponsemessageweget.Angular2providesanObservableoperatortocatcherrors,injectinginitssignaturetheResponseobjectwerequiretoinspectthepreviousproperties:
http.get('/api/bio')
.map(res:Response=>res.txt)
.subscribe(bio=>this.bio=bio)
.catch(error:Response=>console.error(error));
Inanormalscenario,youwouldwanttoinspectmoredataratherthantheerrorproperties,asidefromloggingthatinformationinamoresolidexceptiontrackingsystem.
www.EBooksWorld.ir
InjectingtheHttpclassandtheHTTP_PROVIDERSmodulessymbolTheHttpclasscanbeinjectedinourowncomponentsandcustomclassesbyleveragingAngular'suniquedependencyinjectionsystem.So,ifweeverneedtoimplementHTTPcalls,weneedtoimporttheclassandbinditasadependencyinthelistofcomponentordirectiveproviders,likethis:
import{Component}from'@angular/core';
import{Http}from'@angular/http';
@Component({
selector:'bio',
providers:[Http],
template:'<div>{{bio}}</div>'
})
classBiography{
bio:string;
constructor(http:Http){
http.get('/api/bio')
.map((res:Response)=>res.text())
.subscribe((bio:string)=>this.bio=bio);
}
}
Inthecodeprovided,wejustfollowupwiththebioexamplethatwepointedoutintheprevioussection.NotehowweareimportingtheHttptypeandinjectingitasadependencythroughtheproviderscollectionpropertyoftheComponentdecorator.
Usually,weneedtoperformmultipleHTTPcallsindifferentpartsofourapplication,soit'susuallyrecommendedtoincludetheHttpclassintherootinjectorratherthanonapercomponentinjectorbasis.TodosoandkeepinginmindthattheHttpclassmightrequireotherproviderssuchastheRequestOptionsclass,Angular2providesasetofinjectablesymbolswrappedinsidetheHTTP_PROVIDERStoken.
WerecommendthatyouusethissetinsteadoftheHttpclasstoinjectthedependenciesrequiredtoperformHTTPrequeststhroughoutyourapplication.Foryourconvenience,itisadvisedtodosoattherootcomponentprovidersproperty,soitsinjectorwillmaketheprovideravailablethroughoutitschildcomponentstree.Takingourpomodoroappasanexample,wewouldneedtoimportitattheAppComponentcodeunitandinjectitintothecomponentprovidersrightaway:
app/app.component.ts
import{Component}from'@angular/http';
import{TIMER_DIRECTIVES}from'./timer/timer';
import{TASKS_DIRECTIVES}from'./tasks/tasks';
import{SHARED_PROVIDERS}from'./shared/shared';
import{HTTP_PROVIDERS}from'@angular/http';
www.EBooksWorld.ir
@Component({
selector:'pomodoro-app',
directives:[TIMER_DIRECTIVES,TASKS_DIRECTIVES],
providers:[SHARED_PROVIDERS,HTTP_PROVIDERS],
...
})
exportdefaultclassAppComponent{}
www.EBooksWorld.ir
Arealcasestudy–servingObservabledatathroughHTTPInthepreviouschapter,werefactoredourentireappintomodels,services,pipes,directives,andcomponentfiles.OneofthoseserviceswaspreciselytheTaskServiceclass,whichisthebreadandbutterofourapp,sinceitdeliversthedatathatweneedtobuildourtasklistandotherrelatedcomponents.
Inourexample,theTaskServiceclasswascontainedwithintheinformationwewantedtodeliver.Inareal-worldscenario,youneedtofetchthatinformationfromaserverAPIorbackendservice.Let'supdateourexampletoemulatethisscenario.First,wewillremovethetaskinformationfromtheTaskServiceclassandwrapitintoanactualJSONfile.Let'screateanewJSONfileinsidethesharedfolderandpopulateitwiththetaskinformationthatwehadhardcodedintheoriginalTaskService.tsfile,nowinJSONformat,though:
app/shared/data/raw-tasks.json
[{
"name":"CodeanHTMLTable",
"deadline":"Jun232015",
"pomodorosRequired":1
},{
"name":"Sketchawireframeforthenewhomepage",
"deadline":"Jun242016",
"pomodorosRequired":2
},{
"name":"StyletablewithBootstrapstyles",
"deadline":"Jun252016",
"pomodorosRequired":1
},{
"name":"ReinforceSEOwithcustomsitemap.xml",
"deadline":"Jun262016",
"pomodorosRequired":3
}]
Withthedataproperlywrappedinitsownfile,wecanconsumeitasifitwereanactualbackendservicefromourTaskServiceclientclass.However,wewillneedtoconductrelevantchangesinourmain.tsfileforthat.ThereasonisthatdespiteinstallingtheRxJSbundlewheninstallingalltheAngular2peerdependencies,thereactivefunctionaloperators,likemap(),donotbecomeavailablestraightaway.Wecouldimportallofthematoncebyinsertingthefollowinglineofcodeatsomestepatthebeginningofourapplicationinitializationflow,suchasthebootstrappingstageinmain.ts:
import'rxjs/Rx';
However,thatwouldimportallthereactivefunctionaloperators,whichwillnotbeusedatallandwillconsumeanunnecessarilyhugeamountofbandwidthandresources.Instead,the
www.EBooksWorld.ir
conventionmarkstoimportonlywhatisneeded,soappendthefollowingimportlineatthetopofthemain.tsfile:
app/main.ts
import'rxjs/add/operator/map';
import{bootstrap}from'@angular/platform-browser-dynamic';
importAppComponentfrom'./app.component';
bootstrap(AppComponent,[]);
Whenareactiveoperatorisimportedthisway,itgetsautomaticallyaddedtotheObservableprototype,beingthenavailableforusethroughouttheentireapplication.
Withallthedependenciesproperlyinplace,thetimehascometorefactorourTaskService.tsfile.Opentheservicefileandlet'supdatetheimportstatementsblock:
app/shared/services/task.service.ts
import{Injectable}from'@angular/code';
import{Task}from'../shared';
import{Http,Response}from'@angular/http';
import{Observable}from'rxjs/Observable';
First,weimportintheHttpandResponsesymbolssothatwecanannotateourobjectslateron.RememberanywaythattheHTTTP_PROVIDERStokenhasalreadybeeninjectedatthetoprootcomponent.TheObservablesymbolisimportedfromtheRxJSlibrarysothatwecanproperlyannotatethereturntypesofourasyncHttprequests.
Nowwewillreplacetheexistingimplementationwiththefollowingone.Basically,theTaskServicemodulesevolveintoaservicewithstatethatkeepsexposingataskStorepropertywherewecanfetchthetasksdataset.ItalsofeaturesanObservablepropertyrepresentingataskfeedwecansubscribetoinordertokeepuptodateofanynewtaskthatcouldbecreatedinthefuture.
TheconstructornowfeaturestheHttpdependencyinjectedandboundasaprivatemembertothehttpfield:
app/shared/services/task.service.ts
@Injectable()
exportdefaultclassTaskService{
taskStore:Task[]=[];
taskFeed:Observable<Task>;
privatetaskObserver:any;
privatedataUrl='/app/shared/data/raw-tasks.json';
constructor(privatehttp:Http){
this.taskFeed=newObservable(observer=>{
this.taskObserver=observer;
www.EBooksWorld.ir
});
this.fetchTasks();
}
privatefetchTasks():void{
this.http.get(this.dataUrl)
.map(response=>response.json())
.map(stream=>stream.map(res=>{
return{
name:res.name,
deadline:newDate(res.deadline),
pomodorosRequired:res.pomodorosRequired,
queued:res.queued
}
}))
.subscribe(
tasks=>{
this.taskStore=tasks;
tasks.forEach(task=>this.taskObserver.next(task))
},
error=>console.log(error)
);
}
}
Let'stakeaminutetoexaminethenewserviceimplementation.WekeepthetaskStorepropertyasusual,andwepopulateitoncewefetchthewholegraphoftasksavailableuponinstantiatingtheservicebycallingthefetchTasks()privatemethodfromtheconstructor.ButwehaveintroducedanObservablememberandanObserverfieldtoo.
Let'sexplaintheirroleindetail.Intheconstructor,weinitializetheObservableinstanceandalsoassigntheObservable'sbuilt-inobservertoourObservermember.Thisway,everytimewewanttonotifyanewtaskinthetasksObservablesequencetotheservicesubscribers,wejustneedtoproceedtoexecutethenext()methodoftheObservermember.Therefore,asasingleton,thetasksserviceexposesadatastorethatispopulatedthemomentitisinstantiatedforthefirsttime,becomingsuchdatafullyavailableforallcomponentsthatwilleventuallyconsumeit.
Ontheotherhand,externalconsumersoftheservicecansubscribetothetaskFeedpropertyandreceivepromptnotificationseverytimeanewtaskisaddedtothesequence.WecouldspawnawebsocketsclientandleveragetheObserverAPItoemitnewtasksthroughourObservablesequenceeverytimeanewtaskiscreatedontheserverside.ThecomponentssubscribingtothatObservablesequencewouldreceivethechangesandreflectthosechangesintheirstateautomaticallywithnoadditionallogicrequired.
Aswecansee,thisdatahandlingpatterngoesastepbeyondthemereasynchronousdataconsumptionflowweareusedtowithpromisesandcallbacks,easingdataupdatesandallowingforabettermanagementofinformationoffline.
Let'sfinishourimplementationbyupdatingtheTaskComponentmodule.Sinceweareusing
www.EBooksWorld.ir
thesameAPIwealreadyhadpreviously,thechangesareminimalandarelimitedtotweakingthengOnInithookmethod:
app/tasks/tasks.component.ts
ngOnInit():void{
this.updateQueuedPomodoros();
this.taskService.taskFeed.subscribe(newTask=>{
this.tasks.push(newTask);
this.updateQueuedPomodoros();
});
}
WeneedtosubscribetothetasksfeedsinceObservablesarecold.Theyarenotinitializeduntilsomeclientactuallysubscribestothem,andtheunderlyingObserverofthetaskFeedObservableatTaskServiceisactuallyrequiredwithintheinternalsubscriptiontothehttp.get()connection.Notsubscribingtoitwouldturnintoanexception.Withthischange,wealsoensuredthatourtaskstablewillremainuptodateshouldanewtaskaddedtotheoveralltasksdatastore,withouthavingtorepopulatethewholetasksproperty.ThecalltoupdateQueuedPomodoros()isintroducedinthecallbackaswellshouldanynewtaskbequeuedbydefault.
Now,executethecodeandyouwillseethetasksseamlesslyrenderedonthetable.
www.EBooksWorld.ir
AddingtaskstoourtasksserviceUnfortunately,ourcodeworksinonewayonlynow:wecanconsumedatafromthetasksJSONfilebutwecannotappendnewtasksifrequired.Ideally,weshouldbeabletoappendourowntasksuponrequestandhavethewholesystemreactingtothesechanges,solet'supdateourimplementationfortheTaskServiceclasstoinsertandaddataskmethod.Appendthefollowingmethodattheendoftheserviceclass:
app/shared/services/task.service.ts
addTask(task:Task):void{
this.taskObserver.next(task);
}
Thisnewmethodalignswithwhatwepointedoutalreadyaboutreactiveserviceinterfaces.AddinganewtaskobjectwillturnintoaneweventpublishedintheObservableeventsstreamoftasks,soanyactivecomponentwhichisalreadyconsumingthedatagraphandissubscribedtoitschangeswillreceiveapromptnotificationofthenewtaskcreatedandthereforecanupdateitsstateaccordingly.
Tryitoutyourself!
Tip
Inallfairness,anynewitemrequestshouldbehandledbymeansofaPOSTrequestandallthepreviousoperationsshouldbeperformeduponresolvingtheHttp.post()request,likethis:
constbody=JSON.stringify(task);
constheaders=newHeaders();
headers.append('Content-Type','application/json');
addTask(task:Task):void{
this.http.post(this.dataUrl,body,headers)
.map(response=>response.json())
.subscribe((task:Task)=>
this.taskObserver.next(task);
}
);
}
Sinceserver-sideimplementationsareoutofthescopeofthisbook,wewillleaveituptoyoutoexperimentwiththeHttpmoduleagainstRESTfulAPIs.
www.EBooksWorld.ir
SummaryAswepointedoutatthebeginningofthischapter,ittakesmuchmorethanasinglechaptertocoverindetailallthegreatthingsthatcanbedonewiththeAngular2HTTPconnectionfunctionalities,butthegoodnewsisthatwehavecoveredprettymuchallthetoolsandclassesweneedtodoso.
Therestisjustlefttoyourimagination,sofeelfreetogotheextramileandputallofthisknowledgeintopracticebycreatingbrandnewTwitterreaderclients,newsfeedwidgets,orblogengines,andassemblingallkindsofcomponentsofyourchoice.Thepossibilitiesareendless,andyouhaveassortedstrategiestochoosefrom,rangingfromPromisestoObservables.YoucanleveragetheincrediblefunctionalitiesoftheReactiveFunctionalextensionsandthetinybutpowerfulHttpclass.
Aswehavealreadyhighlighted,theskyisthelimit.Butwestillhavealongandexcitingwayahead.Nowthatweknowhowtoconsumeasynchronousdatainourcomponents,let'sdiscoverhowwecanprovideabroaderuserexperienceinourapplicationsbyroutingusersintodifferentcomponents.Wewillcoverthisinthenextchapter.
www.EBooksWorld.ir
Chapter7.RoutinginAngular2Inthepreviouschapters,wedidagreatjobseparatingconcernsinourapplicationsandaddingdifferentlayersofabstractiontoincreasethemaintainabilityinourPomodoroapp.However,wehaveneglectedthevisualsideofthingsandtheuserexperiencepart.
Atthismoment,ourUIisbloatedwithcomponentsandstuffscatteredacrossasinglescreen,andweneedtoprovideabetternavigationalexperienceandalogicalwaytochangetheapplication'sstateintuitively.
Thisisthemomentwhereroutingacquiresspecialrelevanceandgivesustheopportunitytobuildanavigationalnarrativeforourapplications,allowingustosplitthedifferentareasofinterestintodifferentpagesthatareinterconnectedbyagridoflinksandURLs.
However,ourapplicationisonlyasetofcomponents,sohowdowedeployanavigationschemebetweenthem?TheAngular2routerwasbuiltwithcomponentizationinmind.Wewillseehowcanwecreateourcustomlinksandmakecomponentsreacttotheminthefollowingpages.Inthischapter,wewill:
DiscoverhowtodefineroutestoswitchcomponentsonandoffandredirectthemtootherroutesTriggerroutesandloadcomponentsinourviewsdependingontherequestedroutePassparameterstoourcomponentsstraightfromourroutesLookatthedifferentcomponentlifecyclehooksbasedontheroutingstagesDefinedifferentURLrepresentationstrategies
www.EBooksWorld.ir
AddingsupportfortheAngular2routerSameaswedidwhenoverviewingtheHttpdirectivesandproviders,allthetypesandtokensrequiredforimplementingroutingsupportinourapplicationscomefromitsownspecificbarrel.ThisbarrelwasalreadyinstalledandconfiguredbackinChapter1,CreatingOurVeryFirstComponentinAngular2,althoughwefoundtwobarrelsrelatedtoroutinginourinstallationandfurtherconfiguration:@angular/routerand@angular/router-deprecated.ThisisbecausetheAngularteamintroducedarevampedroutingmechanismwhenswitchingversionsfromBetatoReleaseCandidate.Thisnewroutingmachinery,whichaimstoreplacetheroutingAPIthatAngularhadbeenimplementingsinceitsAlphaversion,alsointroducedrelevantbreakingchangeswithitspreviousincarnation.InordertoensurethatapplicationsbuiltontopofthepreviousroutercouldupgradetoAngular2ReleaseCandidateseamlesslyandpreventmajorissues,theAngularteammadeavailableasnapshotoftheBetaRouter,availablefromthe@angular/router-deprecatedbarrel.Thatiswhyweinstalledandconfiguredtworoutingpackages.
Tip
Atthetimeofclosingthewritingofthisbook,thenewAngular2Routerisstillinaveryearlystageandlackssupportforseveralfunctionalitiesthatarecommonlyusedinourwebapplicationsonadailybasis.ThatiswaythischapterwillfocusondevelopingapplicationsontopofthedeprecatedrouteryetwewillhighlightthedifferencesbetweenitsAPIandthenewerAngular2Routerwhereaspossible.Allinallthedifferencesareminimalandlearninghowtousethedeprecatedrouterinterfacewillbecomepricelessforgettinguptospeedwiththenewrouteronceitbecomesfinal.Pleaserefertothebookcoderepositorytocheckthelatestversionofthecode.
WealsoneedtoinformAngularaboutthebasepathwewanttouse,soitcanproperlybuildandrecognizetheURLsastheuserbrowsesthewebsite,aswewillseeinthenextsection.Ourfirsttaskwillbetoinsertabasehrefstatementwithinour<HEAD>element.Appendthefollowinglineofcodeattheendofyourcodestatementinsidethe<head>tag:
index.html
<basehref="/">
Thebasetaginformsthebrowseraboutthepathitshouldfollowwhileattemptingtoloadexternalresources,suchasmediaorCSSfiles,onceitgoesdeeperintotheURLhierarchy.
Now,wecanstartplayingaroundwithallthegoodiesexistingintherouterlibrary.Priortothis,wewouldneedtoinformthedependencyinjectorabouthowitcaninstantiatethetokenswewillrequirelateronwhileimplementingtheroutingfeaturesinourcomponents.AlltheseprovidersareaccessiblefromtheROUTER_PROVIDERSsymbol.InasimilarfashionaswedidwithHTTP_PROVIDERS,weneedtodeclareitintheproviderspropertyofthetoprootcomponentsothatitisavailableforallitschildcomponents'injectors.
www.EBooksWorld.ir
Openyourtopcomponentmoduleandappendthefollowingimportstatementtotheexistingblockofimportedsymbols.Then,addittotheproviderspropertyofthecomponentdecorator:
app/app.component.ts
...
import{SHARED_PROVIDERS}from'./shared/shared';
import{HTTP_PROVIDERS}from'@angular/http';
import{ROUTER_PROVIDERS}from'@angular/router-deprecated';
@Component({
selector:'pomodoro-app',
directives:[ROUTER_DIRECTIVES],
providers:[SHARED_PROVIDERS,HTTP_PROVIDERS,ROUTER_PROVIDERS],
template:`
...
www.EBooksWorld.ir
SettinguptherouterserviceWiththeprovidersanddirectivesinplace,ourfirststepwillbetoturnourmainhostcomponentintoaroutercomponent.Basically,anycomponentcanbecomearoutingcomponentjustbyconformingtothefollowingrequirements:
Justlikethecomponentclassisflaggedwitha@Componentdecorator,wewanttodecorateitwitha@RouteConfigdecorator.The@RouteConfigdecoratorisconfiguredwithanarrayofRouteDefinitions,whicharebasicallyobjectliteralsdefiningapathidentifiedwithanameandpointingtoacomponenttype.Thecomponentdecoratedwiththe@RouteConfigdecoratoristhensupposedtoincludeaRouterOutletdirectiveinitstemplate.Thiselementwillbecometheplaceholderwherethecomponentswillbeloadedandrendereduponloadingaroutepointingtoeachofthem,removinganypreviouscomponentexistingthere,ifany.
Inthissense,itisrighttosaythattherouterwatchesforstatechangesinthebrowserURLandthensearchesforaRouteDefinitionobjectwhosepathpropertymatchestheexistingURL.Then,itinstantiatesthecomponentdefinedinsuchroutedefinitioninsidetheplaceholderrepresentedbytherouteroutletdirective,whichismeanttoliveinthetemplatebelongingtothecomponentdecoratedwiththatrouterconfiguration.
Let'sseeallthisthrougharealexample.Aswementionedwhileintroducingthischapter,ourapplicationneedsabetternavigationarchitectureinordertobemoreusableandintuitive.AftersplittingallourlogicintodifferentcomponentsinChapter5,BuildinganApplicationwithAngular2Components,wewilldefinedifferentroutestouseeachone,implementingthefollowinglogic:
Theuserreachesourappandchecksthecurrentlistingofthetaskspendingtobedone.TheusercanschedulethetaskstobedoneinordertogettherequiredtimeestimationforthenextPomodorosession.Ifdesired,theusercanjumpontoanotherpageandseeacreatetaskform(wewillcreatetheform,butwillnotimplementitseditingfeaturesuntilthenextchapter).TheusercanchooseanytaskatanytimeandbeginthePomodorosessionrequiredtoaccomplishit.Theusercanmovebackandforthacrossthepagessheorhehasalreadyvisited.
www.EBooksWorld.ir
BuildinganewcomponentfordemonstrationpurposesSofar,wehavebuilttwowell-differentiatedcomponentswecanleveragetodeliveramultipagenavigation.Butinordertoprovideabetteruserexperience,wemightneedathirdone.WewillnowintroducetheformcomponentwewillbeelaboratingmorethoroughlyinChapter8,FormsandAuthenticationhandlinginAngular2,asawaytohavemorenavigationoptionsinourexample.
Wewillcreateacomponentinourtasksfeaturefolder,anticipatingtheformwewilluseinthenextchaptertopublishnewtasks.Createthefollowingfilesinthelocationspointedoutforeachone:
app/tasks/task-editor.component.ts
import{Component}from'angular2/core';
import{ROUTER_DIRECTIVES}from'angular2/router';
@Component({
selector:'pomodoro-tasks-editor',
directives:[ROUTER_DIRECTIVES],
templateUrl:'app/tasks/task-editor.component.html'
})
exportdefaultclassTaskEditorComponent{
constructor(){}
}
app/tasks/task-editor.component.html
<formclass="container">
<h3>TaskEditor:</h3>
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Taskname"
required>
</div>
<divclass="form-group">
<inputtype="Date"
class="form-control"
required>
</div>
<divclass="form-group">
<inputtype="number"
class="form-control"
placeholder="Pomodorosrequired"
min="1"
max="4"
required>
</div>
<divclass="form-group"><inputtype="checkbox"name="queued">
www.EBooksWorld.ir
<labelfor="queued">thistaskbydefault?</label>
</div>
<p>
<inputtype="submit"class="btnbtn-success"value="Save">
<ahref="/"class="btnbtn-danger">Cancel</a>
</p>
</form>
Thisisthemostbasicdefinitionofacomponent,butwewillalsobringtheROUTER_DIRECTIVESsymbolfromtherouterlibrary.Thiswillprovideussupport,aswewillseelateron,toincluderoutingdirectivesinourHTMLtemplate.Thiswillbeusedtointroducelinksinourtemplatetojumptoothercomponents,aswewillseeshortly.Lastbutnotleast,weneedtoexposethisnewcomponentfromourfeaturefolderfacade:
app/tasks/tasks.ts
importTasksComponentfrom'./tasks.component';
importTaskEditorComponentfrom'./task-editor.component';
importTaskTooltipDirectivefrom'./task-tooltip.directive';
constTASKS_DIRECTIVES:any[]=[
TasksComponent,
TaskEditorComponent,
TaskTooltipDirective
];
export{
TASKS_DIRECTIVES,
TasksComponent,
TaskEditorComponent,
TaskTooltipDirective
};
www.EBooksWorld.ir
ConfiguringtheRouteConfigdecoratorwiththeRouteDefinitioninstancesInordertoachievethesegoals,weneedtostartbuildingourtoprouter,whichwillbeinchargeofkickingofftheroutes'scaffolding.Thelogicalpathbeginsinourtoprootcomponent.Openitsfilemoduleandimportthefollowingtokens,rightnexttotheROUTER_PROVIDERSsymbolweimportedatthebeginningofthischapter.Thecodeisasfollows:
app/app.component.ts
...
import{
ROUTER_PROVIDERS,
RouteConfig,
ROUTER_DIRECTIVES
}from'@angular/router-deprecated';
import{TimerComponent}from'./timer/timer';
import{
TasksComponent,
TaskEditorComponent}from'./tasks/tasks';
...
TheRouteConfigrepresentsthedecoratortypethatwillturnourcomponentintoaroutercomponent.TheROUTER_DIRECTIVESsymbolwrapstheviewdirectiveswewillneedshortlytolinktotheseroutes.Wealsoimportthetokensofallthethreecomponentswewillbedealingwith.Aswewillshortlysee,eachrouteneedstodeclarethetypeofthecomponentweareroutingthebrowserto.
Let'scontinuebyreplacingthedirectivesinourAppComponentmodulebytheROUTE_DIRECTIVESsymbol,sincewewillnotneedtodeclarethefacadetokensofthecomponentsthatlivedinitstemplateanymore.Therouterwillhandlethisforus:
app/app.component.ts
@Component({
selector:'pomodoro-app',
directives:[ROUTER_DIRECTIVES],
providers:[SHARED_PROVIDERS,HTTP_PROVIDERS,ROUTER_PROVIDERS],
template:`
...
})
Now,let'sexpandthecomponentclassdefinitionwiththeRouteConfigdecoratorbyappendingthefollowingdecoratorrightafterthe@Componentdecoratorblockandbeforetheclassstatement:
app/app.component.ts
www.EBooksWorld.ir
@RouteConfig([
{path:'',
name:'TasksComponent',
component:TasksComponent
},
{
path:'tasks/editor',
name:'TaskEditorComponent',
component:TaskEditorComponent
},{
path:'timer',
name:'TimerComponent',
component:TimerComponent
}
])
exportdefaultclassAppComponent{}
Aswepointedoutatthebeginningofthischapter,theRouteConfigdecoratormustbepopulatedwithanarrayofRouteDefinitionobjects,eachonespecifyingapaththat,oncereachedbytheuser,willenablethecomponentwhosetypewehavedefinedinthecomponentproperty.
Tip
Note:ThenewRouterreplacedthe@RouteConfigdecoratorbythe@Routesdecorator.Thenamepropertyisremovedfromtheroutedefinitionsschemaandroutematchingisperformedjustbycheckingthepathvalue.
Inthepreviousexample,ourhostcomponentwillreacttothreedifferentroutesandthusservetheTasksComponentitem,theTimerComponentitem,ortheTaskEditorComponentitemdependingontheroute'spath.
Tip
HerewestumbleuponanothercommonconventioninthepreviousversionfotheAngular2router:namingrouteswiththesamenameasthecomponenttheyreferto.Aswewillseeshortly,wewilluseeachroutenameforpopulatingthelinkspointingtoeachresource,sonamingroutesafterthecomponenttheywillactivatebecomesprettyusefulandintuitivewhenitcomestoassessingthetargetofeachlinkfoundinthetemplate.Thisconventionisnolongerenforcedinthenewrouter,sinceonlypathsareusedtoperformroutematching.
Therearetwoquestionsatthispoint:wherewillthesecomponentsberenderedandhowwillwetriggereachroute?Inordertoanswerthesequestions,weneedtolookintotherouterdirectivesindetail.
www.EBooksWorld.ir
Therouterdirectives–RouterOutletandRouterLinkTheROUTER_DIRECTIVESsymbolgivesusaccesstotheonlytwodirectiveswewillneedinourapplications.
First,theRouterOutletdirectiveistheplaceholderdirectivewherethedifferentcomponentswhosepathshavebeennavigatedtobytheuserwillberendered.TheRouterLinkisusedasanattributedirectivetohelptheHTMLcontrolsbehaveasanchorsorlinkbuttonsleadingtothedifferentroutesbyspecifyingtheuniquenameeachrouteisconfiguredwith,aswewillseenext.
Inthepreviouschapters,weconfiguredourtoprootcomponenttemplatetodisplayacutenavbarheader,followedbythecustomelementsrepresentingthePomodorotimerandthetaskslist.Now,wewillstriptheHTMLtemplateoutfromthecomponentdecoratordefinitionandsaveitintoitsowntemplatefile,inordertoaccessitmoreconvenientlywheneditingtheHTMLisrequired.Also,wewillrefactoritintoarouter-friendlycomponenttemplatewithlinkspointingtothedifferentviewsorstatesourapplicationcanfeature,asfollows:
app/app.component.ts
...
@Component({
selector:'pomodoro-app',
directives:[ROUTER_DIRECTIVES],
providers:[SHARED_PROVIDERS,HTTP_PROVIDERS,ROUTER_PROVIDERS],
templateUrl:'app/app.component.html'
})
...
app/app.component.html
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroApp</strong>
</div>
<ulclass="navnavbar-navnavbar-right">
<li><a[routerLink]="['TasksComponent']">Tasks</a></li>
<li><a[routerLink]="['TimerComponent']">Timer</a></li>
<li><a[routerLink]="['TaskEditorComponent']">
PublishTask</a>
</li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
Asyoucanseeinthetemplate,thelocationwherethecomponentsusedtolivehasbeenreplacedbythe<router-outlet>directive,andthreenewlinkscompoundupourbeautiful
www.EBooksWorld.ir
navbar.Reloadthepageandseehowourtaskslistisrenderedonthescreen,andthenclickontheTimerlink.Awesome!TheTimerComponentitemjustloadedonthescreen.Clickontheotherlinkorhitbackonyourbrowsertoseehowyoucanjumpacrossthedifferentcomponentsseamlessly.
Let'stakealookattheselinksmoreclosely,takingthelinkpointingtothetimerasanexample:
<a[routerLink]="['TimerComponent']">Timer</a>
ThemorphologyoftherouterLinkdirectiveisprettyself-explanatory.Ontheright-handsideoftheequalsymbol,itexpectsanarrayofroutenamescorrespondingtothenamedrouteand,optionally,thesubrouteswithintheformerthatwewanttonavigateto.Mostofthetime,wewilljustseeoneuniquestringvalue.However,thearraycanallocatemanyvaluesdependingonwhetherthecomponentwhosenamedpathwearepointingtohostsitsownrouterwithnamedroutesaswell.Inthiscase,thesubroutenameswewanttoloadwillbedeclarednextinthestringsarray.Thisiswhyitissoconvenientandrecommendedtonameourroutesafterthecomponentstheypointat.
Note
ThenewReleaseCandidateRouterdeprecatesnamedroutes,favoringURLpathsinstead.Therefore,the[routerLink]directivewillexpectafullpathasavalue.
TherouterLinkdefinitionalsoleavesroomtoaddparametersdeclaratively,sowecantriggerdynamicroutesatruntime.Wewilltapintoallthesefeaturesthroughoutthenextsections,butnowweneedtoansweroneimportantquestion.Whatifwewanttonavigatetoacomponentimperativelywithoutactuallyclickingonalink,butratherastheby-productofanactionperformedwithinthecomponent'scontrollerclass?
www.EBooksWorld.ir
TriggeringroutesimperativelyPerhaps,youwouldliketojumponourtimerbyselectingthetaskwewanttoworkon.Wehavealreadysetupabehaviorthatdisplayedaqueuedlabelwhenevereachtaskwaspickedforbeingdone.Wewillleveragethesamebehaviortocreateaworkonbuttonthatwillredirecttheusertothetimercomponent.
First,let'sinjecttheRoutertypeasadependencyinourTasksComponentmodule,sowecangainaccessimperativelytoitsmethods.SincewealreadydeclaredtheROUTER_PROVIDERSsymbolwhilebootstrappingtheapplication,Angular2willtakecareofinjectingtheroutertypeifproperlydeclaredinourcomponent,solet'sdoit.Addthefollowingimportstatementatthetopofthecomponent,rightaftertheexistingones:
app/tasks/tasks.component.ts
import{Router}from'@angular/router-deprecated';
Now,let'supdatetheconstructortoinjecttheroutertype.Wewillmarktheconstructorasaprivateformsothatitbecomesprivatelyavailablefromthecomponentmembers:
app/tasks/tasks.component.ts
...
constructor(
privatetaskService:TaskService,
privatesettingsService:SettingsService,
privaterouter:Router){
this.tasks=this.taskService.taskStore;
this.today=newDate();
this.queueHeaderMapping=settingsService.pluralsMap.tasks;
this.timerMinutes=settingsService.timerMinutes;
}
...
Now,let'saddamethodrightbelowtheupdateQueuedPomodoros()method,whichwillleadtheuserimperativelytothetaskrouteuponexecutingit:
workOn():void{
this.router.navigate(['TimerComponent']);
}
Howdoweexecuteit?Let'sintroduceanewbuttoninourtable,nexttothetoggletaskbuttonatthelastcelloneachrow,withaclickhandlerpointingtotheprecedingmethod.Thecodeisasfollows:
app/tasks/tasks.component.html
<td>
<buttontype="button"class="btnbtn-defaultbtn-xs"
[ngSwitch]="task.queued"
www.EBooksWorld.ir
(click)="toggleTask(task)">
<template[ngSwitchWhen]="false">
<iclass="glyphiconglyphicon-plus-sign"></i>
Add
</template>
<template[ngSwitchWhen]="true">
<iclass="glyphiconglyphicon-minus-sign"></i>
Remove
</template>
<templatengSwitchDefault>
<iclass="glyphiconglyphicon-plus-sign"></i>
Add
</template>
</button>
<buttontype="button"
class="btnbtn-defaultbtn-xs"
*ngIf="task.queued"
(click)="workOn()">
<iclass="glyphiconglyphicon-expand"></i>Start
</button>
</td>
AconvenientNgIfdirectivewilldisplaythebuttononlywhenrequired.Wehaveincludedaniconforcosmeticpurposes,butfeelfreetoremoveitorreplaceitbyanyotherglyphofyourchoice.
Nowreloadthetable,setoutanytasktobedone,andclickonthebuttonthatappears.Voila!Youwillberedirectedtothetimertobeginworkingonthattaskifdesired.
Note
Thenavigate()methodofthenewRouterwillexpectastringcontainingthefullpathinstead.
www.EBooksWorld.ir
CSShooksforactiveroutesWehaveseenhowtoturnanylinkorDOMelementintoahyperlinkpointingtoanamedroutethatinstantiatesacomponent.However,itwouldbegreattoprovidesomekindofvisualcueabouttheactivelinkatanygiventime.ThatispreciselyoneofthesidefeaturesimplementedintherouterLinkdirective:whenevertheroutedefinedonarouterLinkdirectivebecomesactive(regardlessofwhethertheuserreachedthatroutedeclarativelyorimperatively),theelementwillbedecoratedwiththerouter-link-activeclass.WecanthereforeintroducespecificCSSdefinitionsinourcomponents'stylesheetstohighlightifaspecificlinkisactiveornot.
Wewillseeallthisfunctionalityinactionjustbytweakingourtopparentcomponentabit.Openthetoprootcomponentcontrollerclassfileandinsertthefollowingstylesheetinhtecomponentdecoratorconfiguration:
app/app.component.ts
@Component({
selector:'pomodoro-app',
directives:[ROUTER_DIRECTIVES],
providers:[SHARED_PROVIDERS,HTTP_PROVIDERS,ROUTER_PROVIDERS],
styles:[`
.router-link-active{
font-weight:bold;
border-bottom:2px#d9534fsolid;
}
`],
templateUrl:'app/app.component.html'
})
Nowreloadthebrowserandclickonanylinkornavigatetoatasktimerfromthetaskstable,keepinganeyeonthevisualstateofthetopnavbar.Theactivelinkwillbeproperlyenhancedwiththestylingwedefined.Ifyouinspectthecode,youwillseetherouter-link-activeclassrenderedontheactivelinkeachtime.
www.EBooksWorld.ir
HandlingrouteparametersWehaveconfiguredprettybasicpathsinourroutessofar,butwhatifwewanttobuilddynamicpathswithsupportforparametersorvaluescreatedatruntime?Creating(andnavigatingto)URLsthatloadspecificitemsfromourdatastoresisacommonactionweneedtoconfrontonadailybasis.Forinstance,wemightneedtoprovideamaster-detailbrowsingfunctionality,soeachgeneratedURLlivinginthemasterpagecontainstheidentifiersrequiredtoloadeachitemoncetheuserreachesthedetailpage.
Wearebasicallytacklingadoubletroublehere:creatingURLswithdynamicparametersatruntimeandparsingthevalueofsuchparameters.Noproblem,theAngularrouterhasgotourbackandwewillseehowusingarealexample.
www.EBooksWorld.ir
PassingdynamicparametersinourroutesWeupdatedthetaskslisttodisplayabuttonleadingtothetimercomponentpagewhenclicked.Butwejustloadthetimercomponentwithnocontextwhatsoeverofwhattaskwearesupposedtoworkononcewegetthere.Let'sextendthecomponenttodisplaythetaskwepickedpriortojumpingtothispage.
First,let'sgetbacktothetaskslistcomponenttemplateandupdatethesignatureofthebuttonthattriggersthenavigationtothetimercomponentinordertoincludetheindexofthetaskitemcorrespondingtothatloopiteration:
app/tasks/tasks.component.html
...
<buttontype="button"
class="btnbtn-defaultbtn-xs"
*ngIf="task.queued"
(click)="workOn(i)">
<iclass="glyphiconglyphicon-expand"></i>Start
</button>
...
RememberthatsuchanindexwasgeneratedateveryiterationoftheNgFordirectivethatrenderedthetablerows.Nowthatthecallincorporatestheindexinitssignature,wejustneedtomodifythepayloadofthenavigatemethod:
workOn(index:number):void{
this.router.navigate(['Timer',{id:index}]);
}
IfthishadbeenarouterLinkdirective,theparameterswouldhavebeendefinedinthesameway:ahashobjectfollowingthepathnamestring(orstrings,aswewillseewhiletappingintothechildrouters)insidethearray.Thisisthewayparametersareaddedtothegeneratedlink.However,ifweclickonanybuttonnow,wewillseethatthedynamicIDvaluesareappendedasquerystringparameters.Whilethismightsufficeinsomescenarios,weareafteramoreelegantworkaroundforthis.So,let'supdateourroutedefinitiontoincludetheparameterinthepath.GobacktoourtoprootcomponentandupdatetherouteinsidetheRouteConfigdecoratorasfollows:
app/app.component.ts
...
},{
path:'timer/:id',
name:'TimerComponent',
component:TimerComponent
}
...
www.EBooksWorld.ir
Refreshtheapplication,schedulethelasttaskonthetable,andclickontheStartbutton.YouwillseehowthebrowserloadstheTimercomponentunderaURLlike/timer/3.
Eachpathcancontainasmanytokensprefixedbyacolonasrequired.ThesetokenswillbetranslatedtotheactualvalueswhenweactonarouterLinkdirectiveorexecutethenavigatemethodoftheRouterclassbypassingahashofthekey/valuepairs,matchingeachtokenwithitscorrespondingkey.So,inanutshell,wecandefineroutepathsasfollows:
{
path:'/products/:category/:id',
name:'ProductsByCategoryComponent',
component:ProductsByCategoryComponent
}
Then,wecanexecuteanygivenroutesuchastheonedepictedearlierasfollows:
<a[routerLink]="['ProductsByCategoryComponent',{
category:'toys',
id:452
}]">SeeToy</a>
Thesameappliestotheroutescalledimperatively:
router.navigate(['ProductsByCategoryComponent',{
category:'toys',
id:452
}]);
www.EBooksWorld.ir
ParsingrouteparameterswiththeRouteParamsserviceGreat!Now,wearepassingtheindexofthetaskitemwewanttoworkonloadingthetimer,buthowdoweparsethatparameterfromtheURL?TheAngularrouterprovidesaconvenientinjectabletype(alreadyincludedinROUTER_PROVIDERS)namedRouteParamsthatwecanusefromthecomponentshandledbytheroutertofetchtheparametersdefinedintheroutedefinitionpath.
Openourtimercomponentandimportitwiththefollowingimportstatement.Also,let'sinjecttheTaskServiceprovider,sowecanretrieveinformationfromthetaskitemrequested:
app/timer/timer-widget.component.ts
import{Component,OnInit}from'@angular/core';
import{SettingsService,TaskService}from'../shared/shared';
import{RouteParams}from'@angular/router-deprecated';
...
Weneedtoalterthecomponent'sdefinitioninordertoassigntheTaskServiceasanannotateddependencyforthiscomponent,sotheinjectorcanproperlyperformtheproviderlookup.
Note
ThenewReleaseCandidaterouterhasdeprecatedtheRouteParamsclass,favoringthenewRouteSegmentsclass,whichexposesmoreandmoreusefulmethodsandhelpers.PleaserefertotheofficialdocumentationforbroaderinsightsonitsAPI.
Wewillalsoleveragethisactiontoinserttheinterpolatedtitlecorrespondingtotherequestedtaskinthecomponenttemplate:
app/timer/timer-widget.component.ts
...
@Component({
selector:'pomodoro-timer-widget',
template:`
<divclass="text-center">
<imgsrc="/app/shared/assets/img/pomodoro.png">
<h3><small>{{taskName}}</small></h3>
<h1>{{minutes}}:{{seconds|number:'2.0'}}</h1>
<p>
<button(click)="togglePause()"class="btnbtn-danger">
{{buttonLabelKey|i18nSelect:buttonLabelsMap}}
</button>
</p>
</div>`
})
...
ThetaskNamevariableistheplaceholderwewillbeusingtointerpolatethenameofthetask.
www.EBooksWorld.ir
Withallthisinplace,let'supdateourconstructortobringboththeRouteParamstypeandtheTaskServiceclassestothegameasprivateclassmembersinjectedfromtheconstructor:
app/timer/timer-widget.component.ts
...
constructor(
privatesettingsService:SettingsService,
privaterouteParams:RouteParams,
privatetaskService:TaskService){
this.buttonLabelsMap=settingsService.labelsMap.timer;
}
...
Withthesetypesnowavailableinourclass,wecanleveragethengOnInithooktofetchthetaskdetailsoftheiteminthetasksarraycorrespondingtotheindexpassedasaparameter.WaitingfortheOnInitstageisnoteasy,sincewewillfindissueswhentryingtoaccessthepropertiescontainedinrouteParamsbeforethatstage:
app/timer/timer-widget.component.ts
ngOnInit():void{
this.resetPomodoro();
setInterval(()=>this.tick(),1000);
lettaskIndex=parseInt(this.routeParams.get('id'));
if(!isNaN(taskIndex)){
this.taskName=this.taskService.taskStore[taskIndex].name;
}
}
Howdowefetchthevaluefromthatidparameter?TheRouteParamsobjectexposesaget(param:string)methodwecanusetoaddressparametersbyname.Inourexample,weretrievedthevalueoftheidparameterbyexecutingtherouteParams.get('id')commandinthengOnInit()hookmethod.Basically,thisishowwegetparametervaluesfromourroutes.First,wegrabaninstanceoftheRouteParamsclassthroughthecomponentinjectorandthenweretrievevaluesbyexecutingitsgetterfunction,whichwillexpectastringparameterwiththenameofthetokencorrespondingtotheparameterweneed.
Note
ItisimportanttonotethatwearefetchingthedataalreadypersistedinthetaskStorepropertyofourTaskServiceprovider.Sinceitisasingleton,availablethroughouttheentireapplicationbymeansoftheAngularDImachinery,whichhadbeenalreadypopulatedatTaskComponent,alltheinformationwerequireisalreadythere.ThingswouldbecometrickierifweloaddirectlyeachtimerURL.Inthosecases,theinformationwouldhavenotbeenfetchedyet,sowewouldhavetosubscribetotheserviceinordertoforceittoloadthedatathroughitsunderlyingHttpclient.WesawthisinChapter6,AsynchronousDataServiceswithAngular2;applyingtheasyncpipetothetaskNameinterpolationinthetemplatewouldbe
www.EBooksWorld.ir
required.Forthesakeofsimplicity,wewillskipthatrefactoringhere,butweencourageyoutotweakthecomponenttoextendsupportforthisscenarioaswell.
www.EBooksWorld.ir
DefiningchildroutersAsourapplicationsscale,theideaofbundlingalltheroutedefinitionsinacentralizedlocation(forexample,therootcomponent)doesnotseemlikeagoodapproach.Themorerouteswedefinethere,theharderitwillbetomaintaintheapplication,letalonethetightcouplingwegeneratebetweenourcomponents(thataremeanttobeasmuchreusableandapplication-agnosticaspossible)andtheapplicationitself.
Thisiswhyitisgenerallyagoodpracticetosplitandwraptheroutedefinitionsthatapplytoaspecificfeaturearoundarouterconfigurationdefinedonaspecificcomponentperfeaturelevel,usuallytherootcomponentthatwrapsthatfeaturecontext.TheAngularteamhadthisideainmindwhentheRouterlibrarywasdesignedandthusimplementedsupporttoextendaroutewithchildrenrouteswhilekeepingtheparentroutefullyagnosticofwhatroutesaredefinedaboveitslayeroffunctionality.
Let'sseeallthisthroughanactualexample.Weupdatedourtimerrecentlytodisplaythenameofthetaskwewantedtoworkonafterselectingitfromthetable.However,whatifwewanttokeepprovidingastandalonetimernotboundtoanyspecifictask?Thisway,wecanleveragethecountdownfunctionalityforanyimpromptutaskwithouthavingtocreateitbeforehand.
So,wewillwanttogiveaccesstothetimerintwoflavors:
timer:Thiswillloadthetimerasitiswithoutpointingtoanyspecifictasktimer/task/{id}:Thiswillloadthetimerspecifyingataskname,where{id}istheindexofthetaskwewanttoloadfromtheoveralltasksarray.
Wewillbeginbyupdatingthemainrootcomponent,nowturnedintoaroutercomponent,toturnthe/timer/:idpathintoapathpointingtoachildroutercomponent.Openthecomponentandreplacetheroutedefinitionpointingtothetimercomponentusingthefollowingdefinition:
app/app.component.ts
{
path:'timer/...',
name:'TimerComponent',
component:TimerComponent
}
That'sit.TheellipsisrightnexttotheroutepathinformstheAngularRouterthatitshouldexpectroutedefinitionsnestedwithinthatcomponent.Theproblemhereisthatacomponentshouldnotroutetoitself,sinceitcannotinstantiateitselfinsideitsownRouterOutletdirective.Thisiswhyweneedtoproxythetimercomponentwitharoutercomponentforthisexample.So,let'screatearoutingcomponentforourtimerinsideitsownfolderforsimplicitysake.Aroutingcomponentis,bydefinition,acomponentwithnoimplementationotherthattoserveasacomponentdispatcherdependingonroutes.Theirimplementation,if
www.EBooksWorld.ir
any,isgenerallyprettylimited,anditbasicallyentailstheRouteConfigdecoratorcontainingtheroutesdeliveredbythatfeaturecontextandtheRouterOutletpresentinitstemplate.Routingcomponentsareindeedagoodwaytodecoupleroutingfunctionalitiesfromthespecificimplementationofeachcomponentinthecontextofthatfeature,ensuringfullreusabilityofitsnon-routingcomponentsofthatfeature.
Openthetimerfeaturefolderandcreateafileforourtimerroutingcomponentwiththefollowingimplementation:
app/timer/timer.component.ts
import{Component}from'@angular/core';
import{RouteConfig,ROUTER_DIRECTIVES}from'@angular/router-deprecated';
importTimerWidgetComponentfrom'./timer-widget.component';
@Component({
selector:'pomodoro-timer',
directives:[ROUTER_DIRECTIVES],
template:'<router-outlet></router-outlet>'
})
@RouteConfig([
{path:'/',name:'GenericTimer',
component:TimerWidgetComponent,
useAsDefault:true},{
path:'/task/:id',
name:'TaskTimer',
component:TimerWidgetComponent
}
])
exportdefaultclassTimerComponent{}
Aswecansee,wehavecreatedacomponentclasswithnoimplementationotherthandispatchingroutestotheTimerWidgetComponentcomponentitself.TheRouteConfigtypeallowsustocreateaproperdecoratorcontainingroutedefinitionsandROUTER_DIRECTIVESwillallowustobindtheRouterOutletdirectiveinthecomponenttemplate.
PleasepayattentiontothenewuseAsDefaultBooleanpropertyinthefirstroutedefinition.Thisinformstherouterthatifnomatchingpathsarefound,theRouterclassshouldloadthisroutebydefault.
Note
Atthetimeofthiswriting,thenewReleaseCandidateRouterstilldoesnotimplementtheuseAsDefaultproperty,butitisplannedtobeimplementedbyitsfinalversion.Pleaserefertotheofficialdocumentationforfurtherdetails.
ItisimportanttonotethatanycomponentactingasaroutercomponentcanhaveitsownimplementationlivinginparalleltotheRouterOutletdirective.JustlikewedidwithAppComponent,wecanprovideadditionalfunctionalitiestoourcomponentotherthanthemereroutingfeatures.
www.EBooksWorld.ir
Allright,wehaveacomponentnowthatcanredirectuserstoourtimercomponentintwoflavors,buthowdowelinktoit?
www.EBooksWorld.ir
LinkingtochildroutesThereisnodifferencewhatsoeverinlinkingtoachildrouterandlinkingtoanygivenroutemanagedbyatoprouter,exceptforthefactthatwewillpopulatetheroutenamesarraywiththenamesofthechildrouteswewanttolinkaswell.OpenthePomodoroTaskListcomponentandrefactortheworkOn()methodtolooklikethis:
workOn(index:number):void{
this.router.navigate(['TimerComponent','TaskTimer',{id:index}]);
}
Here,wearetellingAngulartolinktotheroutenamedTimerComponent(hencetheimportanceofnamingourroutesaftereachtargetcomponent'sname).Sincethisisaparentroute(remembertheellipsis)configuredatourtoprootrouter,weneedtoprovidethenameofthechildroutetoloadfromwithintheroutesconfiguredatthechildrouterlevel,TaskTimerinthiscase.Obviously,wewillcompounduptheroutewiththeIDinformationrequiredforloadingthetaskrequested.Clickonanytaskandseehowthetimerisloaded,displayingthetasknamewewishtoworkon.
ThisimplementationapproachgivesthecomponentthechancetobedisplayedwithorwithoutTaskIDinformation.Thisway,wecankeeponbrowsingtothetimerfunctionality,eitherfromthetopnavlinkorbyclickingtheStartbuttonatthetaskstable.
JustrememberweconfiguredthemainroutedefinitionwiththeuseAsDefaultpropertysetastrue,remember?Thismeansthatanythingpointingtothetimerroutewilldegradegracefullytothislastroutedefinitiononcewereachthechildroutedomain.
www.EBooksWorld.ir
TheRouterlifecyclehooksJustlikecomponentsgothroughasetofdifferentphasesduringtheirlifetime,aroutingoperationgoesthroughdifferentlifecyclestages.Eachoneisaccessiblefromadifferentlifecyclehookwhich,justlikecomponents,canbehandledbyimplementingaspecificinterfaceinthecomponentsubjectoftheroutingaction.Theonlyexceptionforthisistheearliesthookintheroutinglifecycle,theCanActivatehook,whichtakestheshapeofadecoratorannotation,sinceitismeanttobecalledbeforethecomponentiseveninstantiated.
www.EBooksWorld.ir
TheCanActivatehookTheCanActivatehook,presentedasadecoratorannotatingthecomponent,ischeckedbytheRouterrightbeforeitisinstantiated.ItwillneeditssetuptobeconfiguredwithafunctionthatisintendedtoreturnaBooleanvalue(oraPromise-typedBooleanvalue)indicatingwhetherthecomponentshouldbefinallyactivatedornot:
@CanActivate((next,prev)=>boolean|Promise<boolean>)
The@CanActivatedecoratoristhereforeafunctionthatexpectsanotherfunctionasanargument,expectingthelattertwoComponentInstructionobjectsasparametersinitssignature:thefirstargumentrepresentstheroutewewanttonavigatetoandthesecondargumentrepresentstheroutewearecomingfrom.Theseobjectsexposeusefulpropertiesabouttheroutewecomefromandthecomponentweaimtoinstantiate:path,parameters,componenttype,andsoon.
Note
Thishookrepresentsagoodpointintheoverallcomponent'slifecycletoimplementbehaviorssuchassessionvalidation,allowingustoprotectareasofourapplication.UnfortunatelytheCanActivatehookdoesnotnativelysupportdependencyinjection,whichmakeshardertointroduceadvancedbusinesslogicpriortoactivatearoute.Thenextchapterswilldescribeworkaroundsforscenariossuchasuserauthentication.
Inthefollowingexample,wepassword-protecttheformsothatitwon'tbeinstantiatedshouldtheuserentersthewrongpassphrase.First,opentheTaskEditorComponentmodulefileandimportallthatwewillneedforourfirstexperiment,alongwithallthesymbolsrequiredforimplementingtheinterfacesfortheroutinglifecyclehookswewillseethroughoutthischapter.Then,proceedtoapplytheCanActivatedecoratortothecomponentclass:
app/tasks/task-editor.component.ts
import{Component}from'@angular/core';
import{
ROUTER_DIRECTIVES,
CanActivate,
ComponentInstruction,
OnActivate,
CanDeactivate,
OnDeactivate}from'@angular/router-deprecated';
@Component({
selector:'pomodoro-tasks-editor',
directives:[ROUTER_DIRECTIVES],
templateUrl:'app/tasks/task-editor.component.html'
})
@CanActivate((
next:ComponentInstruction,
prev:ComponentInstruction):boolean=>{
www.EBooksWorld.ir
letpassPhrase=prompt('Saythemagicwords');
return(passPhrase==='opensesame');
}
)
exportdefaultclassTaskEditorComponent{
...
Asyoucansee,wearepopulatingthe@CanActivatedecoratorwithanarrowfunctiondeclaringtwoComponentInstructiontypedarguments(whicharenotactuallyrequiredforourexample,althoughtheyhavebeenincludedhereforinstructionalpurposes).ThearrowfunctionreturnsaBooleanvaluedependingonwhethertheusertypesthecorrectcase-sensitivepassphrase.Wewouldadviseyoutoinspectthenextandpreviousparametersintheconsoletogetyourselfmoreacquaintedwiththeinformationthesetwousefulobjectsprovide.
Bytheway,didyounoticethatwedeclaredtheROUTER_DIRECTIVEStokeninthedirectivesproperty?Theroutingdirectivesarenotrequiredforouroverviewofthedifferentroutinglifecyclehooks,butnowwearetweakingthiscomponentandwillkeepupdatingittotestdrivethedifferentlifecyclehooks.Let'sintroduceaconvenientbackbutton,leveragingtheCancelbuttonalreadypresentinthecomponenttemplate:
app/tasks/task-editor.component.html
<formclass="container">
...
<p>
<inputtype="submit"class="btnbtn-success"value="Save">
<a[routerLink]="['TasksComponent']"class="btnbtn-danger">
Cancel
</a>
</p>
</form>
www.EBooksWorld.ir
TheOnActivateHookTheOnActivatehookallowsustoperformcustomactionsoncetheroutenavigationtothecomponenthasbeensuccessfullyaccomplished.Wecaneasilyhandleitbyimplementingasimpleinterface.Thesecustomactionscanevenencompassasynchronousoperations,inwhichcase,wejustneedtoreturnaPromisefromtheinterfacefunction.Ifso,theroutewillonlychangeoncethepromisedhasbeenresolved.
Let'sseeanactualexamplewherewewillintroduceanewfunctionalitybychangingthetitleofourformpage.Todoso,wewillkeepworkingontheTaskEditorComponentmoduletobringsupportfortheOnActivatehookinterfaceandtheTitleclasswhoseAPIexposesutilitymethods(https://angular.io/docs/ts/latest/api/platform/browser/Title-class.html)tosetorgetthepagetitlewhileexecutingapplicationsinwebbrowsers.Let'simporttheTitlesymbolanddeclareitasaproviderinthecomponenttomakeitavailablefortheinjector(youcanalsoinjectitearlieratthetoprootcomponentshouldyouwishtointeractwiththisobjectinothercomponents):
app/tasks/task-editor.component.ts
import{Component}from'@angular/core';
import{
ROUTER_DIRECTIVES,
CanActivate,
ComponentInstruction,
OnActivate,
CanDeactivate,
OnDeactivate}from'@angular/router-deprecated';
import{Title}from'@angular/platform-browser';
@Component({
selector:'pomodoro-tasks-editor',
directives:[ROUTER_DIRECTIVES],
providers:[Title],
templateUrl:'app/tasks/task-editor.component.html'
})
Now,let'simplementtheinterfacewithitsrequiredrouterOnActivatemethod.Asaruleofthumb,allrouterlifecyclehooksarenamedafterthehooknameprefixedbyrouterinlowercase:
app/tasks/task-editor.component.ts
exportdefaultclassTaskEditorComponentimplementsOnActivate{
constructor(privatetitle:Title){}
routerOnActivate(
next:ComponentInstruction,
prev:ComponentInstruction):void{
this.title.setTitle('WelcometotheTaskForm!');
}
www.EBooksWorld.ir
}
PleasenotehowweinjecttheTitletypeintheclassthroughitsconstructorandhowwelateronexecuteitwhentherouteractivatesthecomponentoncethenavigationhasfinished.Saveyourchangesandreloadtheapplication.YouwillnoticehowthebrowsertitlechangesoncewesuccessfullyaccessthecomponentafterpassingtheCanActivateandOnActivatestages.
www.EBooksWorld.ir
TheCanDeactivateandOnDeactivatehooksJustlikewecanfilterifthecomponentwearenavigatingtocanbeactivated,wecanapplythesamelogicwhentheuserisabouttoleavethecurrentcomponenttowardsanotheronelocatedelsewhereinourapplication.AswesawincaseoftheCanActivatehook,wemustreturnaBooleanoraPromiseresolvingtoaBooleaninordertoallowthenavigationtoproceedornot.WhentheCanDeactivatehookreturnsorisresolvedtotrue,theOnDeactivatehookisexecutedjustliketheOnActivatehookafterthenavigationisaccomplished.
Inthefollowingexample,wewillinterceptthedeactivationstagesoftheroutinglifecycletofirstinterrogatetheuserwhetherhewantstoleavethecomponentpageornot,andthenwewillrestorethepagetitleifso.Forbothoperations,wewillneedtoimplementtheCanDeactivateandOnDeactivateinterfacesinourcomponent.Thecodeisasfollows:
exportdefaultclassTaskEditorComponentimplementsOnActivate,CanDeactivate,
OnDeactivate{
constructor(privatetitle:Title){}
routerOnActivate():void{
this.title.setTitle('WelcometotheTaskForm!');
}
routerCanDeactivate():Promise<boolean>|boolean{
returnconfirm('Areyousureyouwanttoleave?');
}
routerOnDeactivate():void{
this.title.setTitle('MyAngular2PomodoroTimer');
}
}
Pleasenotethatwehaveremovedthe(next:ComponentInstruction,prev:ComponentInstruction)argumentsfromourhookimplementationsbecausetheywereofnousefortheseexamples,butyoucanaccessalotofinterestinginformationthroughtheminyourowncustomimplementations.
SameastheCanActivatehook,theCanDeactivatehookmustreturnaBooleanvalueoraPromiseresolvedtoaBooleanvalueinordertoallowtheroutingflowtocontinue.
www.EBooksWorld.ir
TheCanReuseandOnReusehooksLastbutnotleast,wecanreusethesameinstanceofacomponentwhilebrowsingfromonecomponenttoanothercomponentofthesametype.Thisway,wecanskiptheprocessofdestroyingandinstantiatinganewcomponent,savingresourcesonthego.
ThisrequiresustoensurethattheinformationcontainedintheparametersandstuffisproperlyhandledtorefreshthecomponentUIorlogicifrequiredinthenewincarnationofthesamecomponent.
TheCanReusehookisresponsibleforallthis,andittellstheRouterwhetherthecomponentshouldbefreshlyinstantiatedorwhetherweshouldreusethecomponentinthefuturecallsofthesameroute.TheCanReuseinterfacemethodshouldreturnaBooleanvalueoraPromiseresolvingtoaBooleanvalue(justliketheCanActivateorCanDeactivatehooksdo),whichinformstheRouterifitshouldreusethiscomponentinthenextcall.IftheCanReuseimplementationthrowsanerrororisrejectedfromwithinthePromise,thenavigationwillbecancelled.
Ontheotherhand,iftheCanReuseinterfacereturnsorresolvestotrue,theOnReusehookwillbeexecutedinsteadoftheOnActivatehookshouldthelatterexistalreadyinthecomponent.Therefore,useonlyoneofthesetwowheneveryouimplementthisfunctionality.
Let'sseealltheseinanactualexample.Whenwescheduleataskinthetasklisttableandproceedtoitstimer,wecanjumpatanytimetothegenerictimeraccessiblefromthetopnavbar,therebyloadinganothertimerthatisnotboundtoanytaskwhatsoever.Bydoingso,weareactuallyjumpingfromoneinstanceoftheTimerWidgetComponentcomponenttoanotherTimerWidgetComponentcomponentandtheAngularrouterwilldestroyandinstantiatethesamecomponentagain.WecansavetheRouterfromdoingsobyconfiguringthecomponenttobereused.OpentheTimerWidgetComponentmoduleandimporttheinterfaceswewillneedforthis,alongwiththesymbolswewereimportingalreadyfromtheRouterlibrary:
app/timer/timer-widget.component.ts
import{Component,OnInit}from'@angular/core';
import{SettingsService,TaskService}from'../shared/shared';
import{RouteParams,CanReuse,OnReuse}from'@angular/router-deprecated';
Now,implementtheCanReuseandOnReuseinterfacesintheclassbyaddingthemtotheimplementsdeclarationandthenproceedtoattachthefollowingrequiredinterfacemethodstotheclassbody:
routerCanReuse():boolean{
returntrue;
}
routerOnReuse(next:ComponentInstruction):void{
//Noimplementationyet
www.EBooksWorld.ir
}
Nowgotothetaskstable,scheduleanytask,andgotoitstimer.ClickontheTimerlinkinthetopnavbar.YouwillseehowtheURLchangesinthebrowserbutnothinghappens.Wearereusingthesamecomponentasitis.Whilethissavesmemoryresources,weneedafreshtimerwhenperformingthisaction.So,let'supdatetheOnReusemethodaccordingly,resettingthetaskNamevalueandthePomodoroitself:
routerOnReuse():void{
this.taskName=null;
this.isPaused=false;
this.resetPomodoro();
}
Reproducenowthesamenavigationjourneyandseewhathappens.Voila!Newbehaviorbutsameoldcomponent.
Advancedtipsandtricks
Althoughwehavediscussedallthatyouneedtostartbuildingcomplexapplicationswithroutingfunctionalities,thereisstillabigcollectionofadvancedtechniquesyoucanusetotakeourapplicationtothenextlevel.Intheupcomingsections,wewillhighlightjustafew.
www.EBooksWorld.ir
RedirectingtootherroutesBesidestheroutedefinitiontypeswehaveseenalready,thereisanotherRouteDefinitiontypenamedRedirectthatisnotboundtoanynamedRouteorcomponent,butwillratherredirecttoanotherexistingRoute.
Sofar,wewereservingthetasklisttablefromtherootpath,butwhatifwewanttodeliverthistablefromapathnamed/taskswhileensuringthatallthelinkspointingtotherootareproperlyhandled?Let'screatearedirectroutethen.WewillupdatethetoprootrouterconfigurationwithanewpathfortheexistinghomepathandaredirectpathtoitfromthenewhomeURL.Thecodeisasfollows:
app/app.component.ts
...
@RouteConfig([{
path:'',
name:'Home',
redirectTo:['TasksComponent']
},{
path:'tasks',
name:'TasksComponent',
component:TasksComponent,
useAsDefault:true
},{
path:'tasks/editor',
name:'TaskEditorComponent',
component:TaskEditorComponent
},{
path:'timer/...',
name:'TimerComponent',
component:TimerComponent
}])
exportdefaultclassAppComponent{}
ThenewredirectingroutejustneedsastringpathpropertyandaredirectTopropertydeclaringthearrayofnamedrouteswewanttoredirectalltherequeststo.
Note
Atthetimeofthiswriting,therotuedefinitionsinthenewRouterstilldonotimplementsupportfortheredirectToproperty.Pleasechecktheonlinedocumentationforamoreup-to-datestatusonthesubject.
www.EBooksWorld.ir
TweakingthebasepathWhenwebeganworkingonourapplicationrouting,wedefinedthebasehrefofourapplicationatindex.html,sotheAngularrouterisabletolocateanyresourcetoloadapartfromthecomponentsthemselves.Weobviouslyconfiguredtheroot/path,butwhatif,forsomereason,weneedtodeployourapplicationwithanotherbaseURLpathwhileensuringtheviewsarestillabletolocateanyrequiredresourceregardlessoftheURLthey'rebeingservedunder?OrperhapswedonothaveaccesstotheHEADtaginordertodropa<basehref="/">tag,becausewearejustbuildingaredistributablecomponentanddonotknowwherethiscomponentwillwinduplater.Whateverthereasonis,wecaneasilycircumventthisissuebyoverridingthevalueoftheAPP_BASE_HREFtoken,whichrepresentsthebasehreftobeusedwithourLocationStrategyofchoice.
Tryitforyourself.Openthemain.tsfilewherewebootstraptheapplication,importtherequiredtokens,andoverridethevalueoftheaforementionedbasehrefapplicationvariablebyacustomvalue:
app/main.ts
import'rxjs/add/operator/map';
import{bootstrap}from'@angular/platform-browser-dynamic';
importAppComponentfrom'./app.component';
import{provide}from'@angular/core';
import{APP_BASE_HREF}from'@angular/common';
bootstrap(AppComponent,[provide(APP_BASE_HREF,{
useValue:'/my-apps/pomodoro-app'
})]);
ReloadtheappandseetheresultingURLinyourbrowsers.
www.EBooksWorld.ir
FinetuningourgeneratedURLswithlocationstrategiesAsyouhaveseen,wheneverthebrowsernavigatestoapathbycommandofarouterLinkorasaresultoftheexecutionofthenavigatemethodoftheRouterobject,theURLshowingupinthebrowser'slocationbarconformstothestandardizedURLsweareusedtoseeing,butitisinfactalocalURL.Nocalltotheserverisevermade.ThefactthattheURLshowsoffanaturalstructureisbecauseofthepushStatemethodoftheHTML5historyAPIthatisexecutedunderthefoldsandallowsthenavigationtoaddandmodifythebrowserhistoryinatransparentfashion.
Therearetwomainproviders,bothinheritedfromtheLocationStrategytype,forrepresentingandparsingstatefromthebrowser'sURL:
PathLocationStrategy:Thisisthestrategyusedbydefaultbythelocationservice,honoringtheHTML5pushStatemode,yieldingcleanURLswithnohash-bangedfragments(example.com/foo/bar/baz).HashLocationStrategy:ThisstrategymakesuseofhashfragmentstorepresentstateinthebrowserURL(example.com/#foo/bar/baz).
RegardlessofthestrategychosenbydefaultbytheLocationservice,youcanfallbacktotheoldhashbang-basednavigationbypickingtheHashLocationStrategyastheLocationStrategytypeofchoice.
Inordertodoso,gotomain.tsandtelltheAngularglobalinjectorthat,fromnowon,anytimetheinjectorrequiresbindingtheLocationStrategytypeforrepresentingorparsingstate(whichinternallypicksPathLocationStrategy),itshouldusenotthedefaulttype,butuseHashLocationStrategyinstead.
Itjusttakestooverrideadefaultproviderinjection:
app/main.ts
import'rxjs/add/operator/map';
import{bootstrap}from'@angular/platform-browser-dynamic';
importAppComponentfrom'./app.component';
import{provide}from'@angular/core';
import{
LocationStrategy,
HashLocationStrategy
}from'@angular/common';
bootstrap(AppComponent,[provide(LocationStrategy,{
useClass:HashLocationStrategy
})]);
Saveyourchangesandreloadtheapplication,requestinganewroute.You'llseetheresultingURLinthebrowser.
www.EBooksWorld.ir
Note
Pleasenotethatanylocation-relatedtokenintheexampleisnotimportedfrom'@angular/router-deprecated'butfrom'@angular/common'instead.
www.EBooksWorld.ir
LoadingcomponentsasynchronouslywithAsyncRoutesAsyouhaveseeninthischapter,eachroutedefinitionneedstobeconfiguredwithacomponentpropertythatwillinformtherouteraboutwhattoloadintotherouteroutletwhenthebrowsersreachthatURL.However,wemightsometimesfindourselvesinascenariowherethiscomponentneedstobefetchedatruntimeorisjusttheby-productofanasynchronousoperation.Inthesecases,weneedtoapplyadifferentstrategytopickupthecomponentweneed.Here'swhereanewtypeofrouterdefinitionnamedAsyncRoutecomestotherescue.ThisspecifickindofrouteexposesthesamepropertiesofthealreadyfamiliarRouteDefinitionclasswehavebeenusingalongthischapter.ItreplacesthecomponentpropertywithaloaderpropertythatwillbelinkedtoaPromisethatresolvesasynchronouslytoacomponentloadedondemand.
Let'sseethiswithanactualexample.Inordertokeepthingssimple,wewillnotbeimportingthecomponentwewanttoloadatruntime,ratherwewillreturnitfromanasynchronousoperation.OpenthetoprootcomponentmoduleandreplacetheroutepointingtoTimerComponentwiththisasyncroutedefinition:
app/app.component.ts
...
@RouteConfig([{
path:'',
name:'Home',
redirectTo:['TasksComponent']
},{
path:'tasks',
name:'TasksComponent',
component:TasksComponent,
useAsDefault:true
},{
path:'tasks/editor',
name:'TaskEditorComponent',
component:TaskEditorComponent
},{
path:'/timer/...',
name:'TimerComponent',
loader:()=>{
returnnewPromise(resolve=>{
setTimeout(()=>resolve(TimerComponent),1000);
});
}
}
])
exportdefaultclassAppComponent{}
Thenexttimeweattempttoloadanyroutebelongingtothetimerbranch(eitherthegenerictimeraccessiblefromthenavbaroranytask-specifictimer),wewillhavetowaituntilthePromiseresolvestothecomponentweneed.Obviously,thegoalofthisexampleisnotto
www.EBooksWorld.ir
teachhowtomakethingsloadslower,buttoprovideasimpleexampleofloadingacomponentasynchronously.
www.EBooksWorld.ir
SummaryWehavenowuncoveredthepoweroftheAngularrouterandwehopeyouhaveenjoyedthisjourneyintotheintricaciesofthislibrary.OneofthethingsthatdefinitelyshinesintheRoutermoduleisthevastnumberofoptionsandscenarioswecancoverwithsuchasimplebutpowerfulimplementation.
Inthischapter,wediscussedhowtoinstallandprovidesupportforroutinginourapplicationsandhowtoturnanygivencomponentintoaroutingcomponentbydecoratingitwiththerouterconfigurationdecoratorandplacingarouteroutletinitstemplate,evenspreadingroutersdownwardinthecomponentstree.Wealsosawhowtodefineregularroutesandsomeotheradvancedtypessuchasredirectorasyncroutes.Theroutinglifecyclehasnosecretsforusanymoreandharnessingitspowerwillopenthedoortodeliveradvancedfunctionalitiesinourapplicationswithnoeffort.Thepossibilitiesareendlessand,mostimportantly,routingcontributestodeliveringabetterbrowsingexperience.
Inthenextchapter,wewillbeefupourtaskeditingcomponenttoshowcasethemechanismsunderlyingwebformsinAngular2andwhatarethebestsstrategiestograbuser'sinputwithformcontrols.
www.EBooksWorld.ir
Chapter8.FormsandAuthenticationHandlinginAngular2Inthepreviouschapter,wecoveredroutingandthisledtosecurityconcernswhenitcametoprovidingdifferenttiersofcontentinourapplication.Enablinguserauthenticationisthefirststepforintroducingrelevantfeaturessuchaspublishingformsinourapplication.However,ifwewanttobuildthesebrandnewfunctionalities,wewillneedtodiscoverhowtosetafoundationfirst.Inthischapter,wewillseehowtobuildformsandthenmoveontocoverhowtoleveragethoseformstoallowuserstologinandcreatenewcontent.
Asawordofcautionaboutthischapter,wewilloverviewdifferentwaysofbuildingforms.Allofthemarevalid,anditsusewilldependonthegoalsyou'reaimingoneverymomenttofulfilleachprojectrequirement.
Inthischapter,wewill:
LearnhowtocreateresponsiveinputcontrolsinourformswithdirectivesDiscusstwo-waydatabindingsupportinAngular2BinddatamodelsandinterfacetypesforformsandinputcontrolsDesigncontrolsetsbothdeclarativelyandimperativelyDiveintothealternativesforinputvalidationBuildourowncustomvalidatorsDevelopourownloginformsImplementgeneral-purposeauthorizationprovidersSecureareasofoursitebyrequestinguserloginupfront
www.EBooksWorld.ir
Two-waydatabindinginAngular2WementionedinpreviouschaptersthatoneofthemaindifferencesbetweenAngular2andthepreviousincarnationsoftheframeworkisthatitdoesnotfavortwo-waydatabindingasthecorepatternofdatamanagement.Well,thisisnotexactlytrue.WhilemostofthedatamanagementprocessesinAngular2areonewayonly,formmanagementprovidesroomfortwo-waydatabindingbymeansoftheNgModeldirective.
Let'sseeallthisthroughanactualexample.Inthepreviouschapter,weintroducedanewcomponentsowecouldexpandtherangeofcomponentsavailableinourappinordertohavemoreoptionsfornavigatingthesite,andthuswecouldbettertestourrouter'simplementation.Thisnewcomponent,namedTaskEditorComponent,hadnoimplementationyetanditstemplatefeaturedthislayout:
app/tasks/task-editor.component.html
<formclass="container">
<h3>TaskEditor:</h3>
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Taskname"
required>
</div>
<divclass="form-group">
<inputtype="Date"
class="form-control"
required>
</div>
<divclass="form-group">
<inputtype="number"
class="form-control"
placeholder="Pomodorosrequired"
min="1"
max="4"
required>
</div>
<divclass="form-group">
<inputtype="checkbox"name="queued">
<labelfor="queued">thistaskbydefault?</label>
</div>
<p>
<inputtype="submit"class="btnbtn-success"value="Save">
<a[routerLink]="['TasksComponent']"class="btnbtn-danger">Cancel</a>
</p>
</form>
www.EBooksWorld.ir
Thisisatinybutniftywebformindeed.Thecomponentincludedsupportforsomeroutinglifecyclehooksinordertoserveasaproof-of-conceptforthedifferentstagesacomponentgoesthroughinitsjourneythroughthenavigationpipeline.Apartfromthat,theformhadnolifewhatsoever—itwasjustanunanimatedcreatureinthemiddleofnowhere.
Note
Youwillseeseveralclassnamesdecoratingourformsthroughthedifferentexamplesincludedinthischapter.Unlesspointedotherwise,allclassnamescontainedinthischapterareborrowedfromtheBootstrapstylesheetforstylingupourform,forexample,container,form-group,form-controlandsoon.Angularhasnorelationshipwiththeseandtheyareindeednotrequiredwhencodingagainsttheframework.
www.EBooksWorld.ir
TheNgModeldirectiveLet'sinfusesomelifeintoitthen!Oneofthegoodthingsaboutimplementingtwo-waydatabindingsupportinourformelementsisthatwedonotneedtoimportanythingupfront.Angular2issmartenoughtodetectwhatitneedsandtheonlydirectivewewillneedisalreadysuppliedout-of-the-box.WeareobviouslyreferringtotheNgModeldirective.AccordingtotheAngular2officialdocumentation:
"ngModelbindsanexistingdomainmodeltoaformcontrol.Foratwo-waybinding,use[(ngModel)]toensurethemodelupdatesinbothdirections."
Inanutshell,intheverymomentwebindanngModelattributetoaformcontrol,thecontrolwillwatchthevaluestoredatthecomponentclasspropertyitisboundtoandwillupdateitselfassoonasthevaluechangesinthemodel.Youmightthink:thisisalreadydonebyAngularwithoutanyrealfanfare.Yes,butthemaindifferencehereisthatsuchsurveillanceisperformedinbothways.Thismeansthattheclassmodelwillupdateitsstateassoonastheformcontrolvalueisupdated.
Enoughsaid!It'stimeforsomeaction.Let'supdateourtaskeditingcomponenttotrythisout.Bringupthecodeofourtaskeditingcomponentand,firstofall,pleasenotethatitfeaturesaCanActivatedecoratorthatposedapassthroughquestiontotheuser.Let'sremoveit,sincewewillencountermoresecureandelegantwaystoprovidesuchfunctionalitylaterinthischapter.Now,let'saddanewmembernamedtaskName,whichwillobviouslyrepresentataskname!
app/tasks/task-editor.component.ts
exportdefaultclassTaskEditorComponentimplementsOnActivate,CanDeactivate,
OnDeactivate{
taskName:string;
constructor(privatetitle:Title){}
//Restofthecomponentremainsunchanged
}
Opentheassociatedtemplateandupdatethefirstinputblocktolooklikethis.Wewillexplainallthisinaminute:
app/tasks/task-editor.component.html
<p>Yourtasknameis{{taskName}}</p>
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Taskname"
[(ngModel)]="taskName">
</div>
www.EBooksWorld.ir
Asyoucansee,wehaveattacheda[(ngModel)]attributedirectiveintoourinputcontrolpointingtothestringpropertywejustcreatedinthecomponentclass,whichisalsoshownonthescreenrightabovetheinput.Executethecodeandchangethetextfieldvalues.Youwillseehowthetextenteredisupdatedinreal-timeonscreen.
ThesyntaxofthengModelgivesaverygoodhinttowhatisitallabout.Weareblendinginasingleattributeaneventhandlerandapropertybinding(hencethecombinationofbracketsplusbraces),sowecaninjectavalueintothetargetcontrolwhilelisteningtochangesmadeonthevalueatthesametime.Inotherwords,itistwo-waydatabinding.
Obviously,thisisaverysimplisticexampleandweaimtobuildsomethingmoreambitious,solet'sleveragethisrecentlygainedexperiencetobuildsomethingmoreuseful.Inthefollowingsection,wearegoingto:
LinktheformtoanewlyinstantiatedtaskobjectactingasamodelPopulatethemodelwiththevaluesenteredintheformPersistthechangesaftervalidatingthedataenteredRedirecttheusertothePomodorostabletoseethetaskjustcreatedthere
www.EBooksWorld.ir
BindingatypetoaformwithNgModelRemovethecodejustaddedandimporttheTaskinterfacetypeintoourformalongwiththeTaskServicemanager,byappendingthefollowingimportstatementtothetop:
app/tasks/task-editor.component.ts
import{Component}from'@angular/core';
import{Title}from'@angular/platform-browser';
import{
Router,
ROUTER_DIRECTIVES,
ComponentInstruction,
CanActivate,
OnActivate,
CanDeactivate,
OnDeactivate}from'@angular/router-deprecated';
import{
Task,
TaskService}from'../shared/shared';
YoumighthavenoticedthatwealsoimportedtheRoutertypefromangular2/router.WewillneedittoredirecttheuserbacktothePomodorolistpageoncethenewtaskhasbeensuccessfullycreated.
Now,weneedtoappendanewTask-annotatedmembertoourclassanddeclareTaskServiceasadependencyintheconstructor,sowecanpersistthenewlycreatedtasklater.RemovethetaskNamestringfieldwecreatedearlierandupdatetheclasswiththesechanges:
app/tasks/task-editor.component.ts
exportdefaultclassTaskEditorComponentimplementsOnActivate,CanDeactivate,
OnDeactivate{
task:Task;
constructor(
privatetitle:Title,
privaterouter:Router,
privatetaskService:TaskService){
this.task=<Task>{};
}
//Restofthecomponentremainsunchanged
}
WehaveaddedanewfieldtotheclassrepresentingtheTaskmodelourformwillbeboundto.SinceTaskisaninterfacetype,wecannotinstantiateitbyusingthenewkeyword,sinceinterfaceshavenoconstructor.However,wecantakeadvantageofgenericsandtypecastanemptyobjecttoenforcetheTaskinterface,aswedidintheprecedingcode.
Ontheotherhand,thetypesdeclaredintheconstructorensurethattheAngularinjectorwill
www.EBooksWorld.ir
makethemavailableasclassfieldsfortherestofthecomponentmembersonceitisinstantiated.
Ideally,wewouldjustneedtolinktheformdatatotheobjectrepresentedbythetaskmemberofourcomponentclass,persistitthroughouttheapplicationbyusingthemethodsalreadycreatedintheTaskServiceclass,andthenproceedtothetasklistrightafterthat.Let'sbeginbyupdatingourHTMLtemplatewiththerequiredngModelattributes,includingaSubmitbuttonandasubmithandlerintheformwrappertag:
app/tasks/task-editor.component.html
<formclass="container"(submit)="saveTask()">
<h3>TaskEditor:</h3>
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Taskname"
[(ngModel)]="task.name">
</div>
<divclass="form-group">
<inputtype="date"
class="form-control"
[(ngModel)]="task.deadline">
</div>
<divclass="form-group">
<inputtype="number"
class="form-control"
placeholder="Pomodorosrequired"
min="1"
max="4"
[(ngModel)]="task.pomodorosRequired">
</div>
<divclass="form-group">
<inputtype="checkbox"
name="queued"
[(ngModel)]="task.queued">
<labelfor="queued">thistaskbydefault?</label>
</div>
<p>
<inputtype="submit"class="btnbtn-success"value="Save">
<a[routerLink]="['TaskList']"class="btnbtn-danger">
Cancel
</a>
</p>
</form>
Therearetworemarkableelementsinthispieceofcode:
NoweachinputcontrolfeaturesanngModeldirectiveattribute,mappedtooneofthepropertiesoftheTasktyperepresentedbythetaskmemberofthecontrollerclass.
www.EBooksWorld.ir
WehaveincludedaSubmitbuttoninourform,althoughtheformtagdoesnotfeatureanyactionattribute,sowherearewesubmittingourformto?The(submit)eventlistenertakescareofhandlingtheeventbybindinganeventhandlertoit.
Ourthreeinputfieldsnowbenefitfromtwo-waydatabindingfunctionality,beingeachinputcontrolpointingtoapropertyexposedbythemodelobject.Whensubmitted,theformwillexecutethesaveTask()methodlocatedinthebodyofourcomponent.Let'stakealookintothismethodthen.Ithasnotbeenaddedalreadytotheclassthoughsopleaseextendourcomponentwithamethodfeaturingsuchanameandappenditanywhereintheclassrightafteritsconstructor:
app/tasks/task-editor.component.ts
saveTask(){
this.task.deadline=newDate(this.task.deadline.toString());
this.taskService.addTask(this.task);
this.router.navigate(['TaskList']);
}
Note
YouhaveprobablyraisedaneyebrowafterwatchingthefirstlineofcodeinthesaveTask()method.Yes,thatisweird.Wegrabthevalueofthedeadlinepropertyjusttoconvertittoastring(itwasaDateobjectalready)andthenweturnitintoaDateobjectagain.Thereisareasonforthis.TheDatePipe(liketheoneweuseintheTasksComponenttemplate)willonlytaketheDateobjectsandtheseneedtobeproperlyformattedsincenolocalizationtransformationisprovidedatthetimeofthiswriting.Thedateinputfielddoesnotsupplysuchlocalizationfunctionalitysoweneedtoensuredataconsistencyacrosstheboardbyrepurposingthedataformatbeforesavingit.Therearebetterworkaroundsforthisbutallofthemarebasicallymoreverboseanddefinitelysitoutsidethescopeofourtopichere,sowewillsticktothisquickfixfortherestofthechapter.
BypassingtheCanDeactivaterouterhookuponsubmittingforms
Nowourcomponenthaseverythingweneedinordertoreflectthechangesmadeonourmodelbytheform.However,ifweattempttofillouttheformwiththedetailsofournexttaskandproceedtosaveit,wewillbeconfrontedwiththatpeskyalertpopupwesetupinthepreviouschapterforinvitingtheuserstofillouttheform,andthat'swhatwejustdidnow!It'stimeforalast-minutechangethen.Let'sinsertabeaconvariableinformingwhethertheformhasbeenupdatedandsuccessfullysavedornot,anduseittoskipthepopuplaterwhererequired.Thecodeisasfollows:
app/tasks/task-editor.component.ts
exportdefaultclassTaskEditorComponentimplementsOnActivate,CanDeactivate,
OnDeactivate{
task:Task;
changesSaved:boolean;
www.EBooksWorld.ir
constructor(
privatetitle:Title,
privaterouter:Router,
privatetaskService:TaskService){
this.task=<Task>{};
}
saveTask(){
this.task.deadline=newDate(this.task.deadline.toString());
this.taskService.addTask(this.task);
this.changesSaved=true;
this.router.navigate(['TaskList']);
}
routerOnActivate(){
this.title.setTitle('WelcometotheTaskForm!');
}
routerCanDeactivate(){
returnthis.changesSaved||confirm('Areyousureyouwanttoleave?');
}
routerOnDeactivate(){
this.title.setTitle('MyAngular2PomodoroTimer');
}
}
Basically,thechangesSavedfieldrepresentsabooleanflagthatwilltakeatruthvaluerightafterpersistingtheTasktypeddatathroughtheTaskServiceAPI.ThisallowstherouterCanDeactivate()methodtoeitherreturntrueassoonasitseeswhetherthechangeshavebeensavedorjustthrowtheconfirmpopup.
Sofarsogood,butnowit'stimetogetfancyandbeautifyourformlogicalittlebit.ValidatingourinputfieldsisdefinitelyagoodstartingpointandthatwillleadustothenextstageinourjourneythroughtheexcitingworldofAngular2forms.
www.EBooksWorld.ir
TrackingcontrolinteractionandvalidatinginputAlthoughwearealreadytrackingchangesinourinputformsthroughthetwo-waydatabinding,weneedabetterwaytowatchtheoverallstateofourform.Inordertodoso,wecantakeadvantageoftheNgFormdirective.TheNgFormdirectivekeepstrackofthestateofallinputcontrolsfoundwithinit.Thegoodnewsisthatsuchadirectivehasbeenpresentinourexamplerightfromthebeginning.How?Basically,theNgFormdirectiveisconfiguredinitsselectortobeattachedtoany<form>tagpresentinourtemplate,providingadditionalfeaturestoourformasreal-timetrackingofthestateoftheinputfieldsinrespectofvalidityanduserinteraction.Inotherwords,ifyouhaveaforminyourtemplate,youhaveangFormdirectivealready.
Tip
YoucancancelthisautomaticbindingbyappendingthengNoFormattributetoany<form>tagyoudonotwanttobeintervenedbyAngular.
Let'sseeallthisthroughasimpleexample.First,markallourfieldswiththeHTML5requiredattribute:
app/tasks/task-editor.component.html
<formclass="container"(submit)="saveTask()">
<h3>TaskEditor:</h3>
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Taskname"
[(ngModel)]="task.name"
required>
</div>
<divclass="form-group">
<inputtype="date"
class="form-control"
[(ngModel)]="task.deadline"
required>
</div>
<divclass="form-group">
<inputtype="number"
class="form-control"
placeholder="Pomodorosrequired"
min="1"
max="4"
[(ngModel)]="task.pomodorosRequired"
required>
</div>
www.EBooksWorld.ir
<divclass="form-group">
<inputtype="checkbox"
name="queued"
[(ngModel)]="task.queued">
<labelfor="queued">thistaskbydefault?</label>
</div>
<p>
<inputtype="submit"class="btnbtn-success"value="Save">
<a[routerLink]="['TaskList']"class="btnbtn-danger">
Cancel
</a>
</p>
</form>
Ifweattempttosubmittheform,automaticalertswillbetriggeredbythebrowserapplyingthevalidationpoliciesenforcedbytheHTML5formvalidationAPI.Butthereisalotmorehappeningunderthehood.Ifweinspectthecodeofourformwiththebrowser'sdevelopertools,wewillseeamyriadofclassnamesdecoratingtheinputcontrolsnow.Wheredidallthesecomefrom?Theanswerissimple!TheNgFormdirective,actioningour<form>tag,putallthesethere.Let'sinspectthefirstinputfieldthroughourbrowser'sdevtoolsasanexample:
<inputtype="text"
class="form-controlng-untouchedng-pristineng-invalid"
placeholder="Taskname"
required="">
Theseclassnames(easilyidentifiablebytheng-prefix)giveusaverygoodhinttothedifferentstatesanygiveninputcontrolcantakewhenwrappedinsidetheNgFormdirective.Theseclassbindingsarefullyreactivetostatechangesinourinputfields.Withyourdevtoolspaneopen,selectanyinputfield,updateitsvalueandthenemptyitagainandseehowtheclassnameschange,assuminganyofthefollowingstates:
Untouched:Whentrue,thecontrolhasnotbeeninteractedwiththeuserTouched:Whentrue,thecontrolhasbeeninteractedwiththeuserPristine:ThecontrolanditsunderlyingmodelhasnotbeenchangedDirty:ThecontrolanditsunderlyingmodelhasbeenchangedValid:TheinnermodelisvalidInvalid:Theinnermodelisnotvalid
Wheninteractingwithourformcontrols,wewillseegeneratedclassnamesmatchingthesestatesrepresentedwiththeng-prefixhereandthere:ng-untouched,ng-pristine,ng-invalid,andsoon.
www.EBooksWorld.ir
TrackingchangeswithlocalreferencesNowthatweknowthatourinputcontrolscanreacttouserinteractionsandmodelvalidation,wecantakeastepfurtherandrendermoreinformationonscreen.Forinstance,wecanstyleourforminareactivefashion:
app/tasks/task-editor.component.ts
@Component({
selector:'pomodoro-tasks-editor',
directives:[ROUTER_DIRECTIVES],
providers:[Title],
templateUrl:'app/tasks/task-editor.component.html',
styles:[`
.ng-valid{border-color:#3c763d;}
.ng-invalid{border-color:#a94442;}
.ng-untouched{border-color:#999999;}
`]
})
...
Ourformwillprovidenowvisualhintsoftheoverallstateofeachinputthroughitsvisuallayout,butperhapsrenderingsomemessagesonscreenwillcompounduptheuserexperiencewewanttodeliverinourapp.Inordertodoso,weneedtogointoeachinputcontrolstate,andtemplatelocalreferencesbecomequitehandyforthis.TheywillprovideavaluableaccessortothegeneralstatehandlerofourformwhichisourngFormdirective.Let'sinsertastatemessageinourformandturnitintoawatcherofthestateofthefirstinputfield,usingngFormasaproxy:
app/tasks/task-editor.component.html
<formclass="container"(submit)="saveTask()">
<h3>TaskEditor:</h3>
<divclass="form-group">
<pclass="text-muted"*ngIf="name.untouched">
Startherebyenteringthetaskname.
</p>
<pclass="text-success"*ngIf="name.valid&&name.touched">
Welldone!That'sagoodnameforatask!
</p>
<pclass="text-danger"*ngIf="!name.valid&&name.touched">
Oops!Youcannotleavethenameblank...
</p>
<inputtype="text"
class="form-control"
placeholder="Taskname"
[(ngModel)]="task.name"
#name="ngForm"
required>
</div>
...
www.EBooksWorld.ir
Let'stakeacloserlookattheprecedingcode.Thefirstblockisprettystraightforward:wewillrenderdifferentmessages(usingthestylingprovidedbyBootstrap,aswedidalreadywiththerestoftheform)dependingoftheoverallstateofthecontrol.Inordertorefertotheinputcontrol,weneedtocreatealocaltemplatevariablesowecanaddressitfromadifferentelement,andsowedobyappendingthe#namelocaltemplatereferenceinourcontrol.Surprisingly,itispopulatedwithavaluethough.LocaltemplatevariablesinAngular2canbepopulatedwithothervalues,orpointerstootherobjectsandthat'sexactlywhatwearedoingbypointing#nametothengFormstring.TheNgFormdirectiveexportsitselfunderthengFormname,soifwerefertoitsnamefromanylocaltemplatereference,wewillgainaccesstoitsAPIand,thus,itsstate.
Let'swrapupourjourneyintoformbuildingbasedonclassicaltwo-waydata-binding.Nevertheless,thereisanotherpowerfulwaytobuildformsinAngular2thathasnothingtodowithourbeloved[(ngModel)]directive,althoughitalsoprovidessupportforthesamefunctionalitiesandevenextendssupportforsomemorefeatures,becomingthepreferredwayforbuildingformsinAngular2.
www.EBooksWorld.ir
Controls,ControlGroups,andtheFormBuilderclassInthischapter,wehaveseenhowtoimplementtwo-waydatabindinginourformstohookupdataentitieswithinputfields.Whilethisapproachisperfectlyfine,Angular2providesamoreefficientformmodelwhereeverythingflowsinonedirectiononly.Therearealotofupsidesforthis,butprobablythemostrelevantreasonistheremarkableimpactonperformancethattraditionaltwo-waydata-bindinghasinourapplications,incomparisontootherpatternswhereinformationflowsinonedirectiononly.
www.EBooksWorld.ir
IntroducingControlsandValidatorsInanutshell,wemightsummarizeControlsinthefollowingstatement:AControlistheminimumrepresentationofanelementbeingpartofaform.Thinkofatextinputoracheckboxasperfectexamplesofacontrol.Infact,wehavebeenusingAngular2controlsthroughoutthischaptereverytimewetappedintoanyoftheinputfieldsofthePomodorotaskcreationform.Angular2creates(bydefault)acontrolforeveryformcomponentexistingwithinaNgFormdirective.Sincetheformelementisattachedtothisdirective,wehavebeendealingwithControlobjectsrightfromthebeginning.
Now,whatdoesittaketocreateaControlobject?Itisprettysimplereally.OnceweimporttheControltypefromtheangular2/commonbarrel,wecancreatecontrolsinourclassesjustbyinstantiatingthemlikethis:
varfirstName=newControl('',Validators.required);
TheControlconstructorisprettysimple:thefirstparameteristhedefaultvalueourcontrolwillassumebydefaultandcanallocateanyobjecttype.Itisusuallypopulatedwithanemptystringwhennodefaultvalueisrequired.ThesecondparameterexpectsafunctionwhichtheControlobjectwillusetovalidatethedatainput.Wecancreateourowncustomvalidationfunction,aswewillseelaterinthischapter,orwecantakeadvantageofanyofthestaticfunctionsalreadycreatedintheValidatorsclass,alsoavailableattheangular2/commonbarrel.Thisclassexposesthefollowingstaticvalidatormethods:
required:Thisrequiresthecontroltohaveanactualnon-emptyvalue.ItisequivalenttotheHTML5requiredattribute.minLength(minLength:number):Thevalidatorwillrequirethecontroltobepopulatedwithavalueofagivenminimumlength.maxLength(maxLength:number):Thevalidatorwillrequirethecontroltobepopulatedwithavalueofagivenmaximumlength.pattern(pattern:string):Theargumentofthepatternvalidatorexpectsastringcontainingaregularexpression.compose(validators:Function[]):Thisisnotanactualvalidatorthough,butamixintocombinevalidatorsinthearraypassedasaparameter.composeAsync():Thisisthesameascompose,butitwillexpecttoconverteachfunctionintoapromiseunderthehoodandexecuteallthematonce,returningtheresultofthevalidationcheckonceallpromiseshaveresolved.
Eachoneofthesevalidators,wheninterrogatedbyAngular,willeitherreturnanullvalueindicatingthattheinputisvalidoranerrormapobjectwithfurtherdetailsoftheerrorscausingtheinvalidationofourcontrol.Don'tpanic!Youwillgetthefullpicturelateronwhenweimplementsomeofthesevalidatorsinarealexample.
www.EBooksWorld.ir
ControlsintheDOM–thengControldirectiveWecancreateControlsinthebodyofourcomponentclassorwecanhaveAngular2createandbindthemdirectlyintoourtemplatesthankstothengControlattributedirective,formalizedintheNgControlNameclass.
Note
DonotconfusetheNgControlNameclasswiththeNgControlclass,whichisthebaseclassthatNgControlNameactuallyextendsfrom.
ThisdirectivecanonlybeusedasachildofaNgFormdirective(whichwecoveredalready)oranelementdecoratedwiththeNgFormModelattributedirective,whichwewillcovershortly.TheofficialAngulardocumentationdescribesthengControlusingthefollowingstatement:
"CreatesandbindsacontrolwithaspecifiednametoaDOMelement.ThisdirectivecanonlybeusedasachildofNgFormorNgFormModel."
Accordingtothisquote,wewillusuallyfindthengControldirectivedecoratinganinputcontrollikethis:
<inputtype="email"ngControl="email">
FromthemomentweintroduceaNgControlNamedirectiveasanamedngControlattributeinourinputcontrols,wecanaccessitsvalue,checkitsvalidity,andwatchitforstatechanges.ThemostconvenientwayistoassignalocaltemplatereferencepointingtotheexportedNgFormdirectiveandevaluateitsproperties:
<inputtype="password"ngControl="password"#pwd="ngForm">
<div*ngIf="!pwd.valid">Passwordisinvalid</div>
Intheprecedingexample,whichismeanttobeexecutedwithinthecontextofaformelement,welabeledtheinputcontrolextendedalreadybythengControldirectivewithalocaltemplatereferencenamed#pwdpointingtongForm.Angulardetectsthisreferenceandresetsthepointertothecontrolitself.Thisway,wecanconvenientlyaccessandinspectthepropertiesoftheControlobjectboundtothengControldirective.YouwillactuallyseethispatternquiteoftenwhenworkingwithformsinAngular2,aswealreadydidintheprevioussections.
Note
ItisworthnotingthatthestaticmethodsoftheValidatorclasshaveacounterpartintheformofattributedirectivesthataresensitivetoelementsdecoratedwiththengControldirective,sowecanapplyvalidationrightoverthem:
<inputngControl="taskName"requiredpattern="[a-zA-Z]*">
<inputngControl="pomodoros"minlength="1"maxlength="5">
www.EBooksWorld.ir
GroupingcontrolsintheDOMwithNgControlGroupUsually,formsincludemorethanoneinputcontrolandthereforethedatamodeltheyrepresentfeaturesseveralfieldsorproperties.Whendefiningasetofinputs,wecantakeadvantageoftheNgControlGroupdirective.ThisdirectivebehavesasareferencewrapperforseveralinputcontrolsdecoratedwiththengControldirective,sowecanbenefitfromasingleentrypointtoinspectthevaluesofeachdifferentinputcontrolcontainedintheNgControlGroup.ThefollowingexampledefinesasimpleformcomprisingtwoinputfieldswrappedwithinaNgControlGroupwhosereferenceispassedtoacomponentmethoduponsubmittingtheform:
import{Component}from'@angular/core';
import{NgControlGroup}from'@angular/common';
@Component({
selector:'hello-form',
template:`
<form(submit)="sayHello(fullName)">
<divngControlGroup="nameControlsGroup"#fullName="ngForm">
<divclass="form-group">
<inputtype="text"
placeholder="Firstname"
ngControl="firstName"
required>
<inputtype="text"
placeholder="Lastname"
ngControl="lastName"
required>
</div>
</div>
<inputtype="submit"value="Saymyname">
</form>
`
})
exportdefaultclassSayHello{
sayHello(controlGroup:NgControlGroup):void{
if(controlGroup.control){
letfirstName=controlGroup.control.value.firstName;
letlastName=controlGroup.control.value.lastName;
alert(`Hello${firstName}${lastName}!`);
}
}
}
WeusedlocaltemplatereferencestointrospecttheNgControlGroupwithitselfusingngFormasaproxy.TheobjectpassedtothesayHello()method,whichisthereferencetotheNgControlGroupitselfandexposes(throughthecontrolproperty)alltheinformationpertainingtotheglobalstateofthegroup:pristine,valid,touched,andsoon.WecanalsoinspecttheNgControlobjectscontainedbythecontrolgroupbyinspectingthecontrolspropertyinsidecontrol.Thevaluepropertyofcontrolreturnsahashobjectrepresentationoftheactualvalueofalltheinputfieldscontained.
www.EBooksWorld.ir
Inthisexample,wearepopulatingtheNgControlGroupwiththenameControlsGroupvalue.ThisstringwillbecomethenameofthecontrolgroupinthecontextofthewrappingNgFormdirective.Ifweslidealocaltemplatereferenceintothe<form>elementandinspectitsowncontrolsproperty,wewillseeanobjectcontainingapropertywiththenamegiventoourgroup(orgroups)pointingtoitsrespectiveNgControlGroupobject.Inspectingthevalueproperty,ontheotherhand,willreturnahashobjectwithasmanypropertiesascontrolgroupsfoundintheformcontainingeachoneanobjectrepresentationofthevaluesoftheinputcontrolstherein.
www.EBooksWorld.ir
DefiningcontrolgroupsimperativelywithControlGroupInthebeginningofthischapter,wesawhowtodeclarecontrolsimperativelyinourcomponentcontrollerclass.Then,wejumpedstraighttotheDOMandcoveredhowtocreatecontrolsrightinthetemplateandalsohowtocreatesubsetsofcontrolsgroupedbytheNgControlGroupdirective.
GroupingcontrolsisacommonoperationandonethatwecandoaswellfromwithinthecomponentcontrollerclassbyinstantiatingControlGroupobjectswiththehelpofanothertypenamedFormBuilder,whichcreatesformobjects(inotherwords,controlgroups)byparsingtheconfigurationoftheinputfieldsofourchoice.Wewillseeanactualexampleinacomponentclassinwhich,afterinjectingtheFormBuilderthroughtheconstructor,weaccessitsmethodstoinstantiateaControlGroupwithotherControlGroupsandControlsnested:
import{Component}from'@angular/core';
import{
Control,
ControlGroup,
FormBuilder,
Validators}from'@angular/common';
@Component({
selector:'my-login',
providers:[FormBuilder],
templateUrl:'my-login.component.html'
})
exportclassLoginComponent{
name:Control;
username:Control;
password:Control;
signupForm:ControlGroup;
constructor(formBuilder:FormBuilder){
this.name=newControl('',Validators.required);
this.username=newControl('',Validators.required);
this.password=newControl('',Validators.required);
this.signupForm=formBuilder.group({
name:this.name,
credentials:formBuilder.group({
username:this.username,
password:this.password
})
});
}
}
Intheprecedingexample,wejustcreatedacomponentfeaturingaControlGrouprepresentingatypicalsign-upform,containedinthesignupFormfieldoftheLoginComponentclass.ThisfieldispopulatedintheconstructorbytheformBuilder.group()methodwithanamecontrolandacredentialcontrolthatareacontrolgroupitself(containingtwoothercontrols),with
www.EBooksWorld.ir
differentflavorsofvalidation.EachoneofthosecontrolsisanactualinstanceoftheControlclass.WhenitcomestoinstantiatingControlobjects,wecanbenefitfromthecontrol()methodoftheFormBuilderclass,savingusfromimportingtheControltokenintotheclassifwedonotneeditelsewhere.Wecandramaticallysimplifytheimplementationofthepreviousexamplebyrefactoringitlikethis:
exportclassLoginComponent{
signupForm:ControlGroup;
constructor(formBuilder:FormBuilder){
this.signupForm=formBuilder.group({
name:this.formBuilder.control('',Validators.required]),
credentials:formBuilder.group({
username:this.formBuilder.control('',Validators.required),
password:this.formBuilder.control('',Validators.required)
})
});
}
}
WecanevengoastepfurtherandtakeadvantageofthesyntaxsugarprovidedbyAngularandthusinstantiatethecontrolsdefinedintheControlGroupthroughamoresimplisticsyntax:
exportclassLoginComponent{
signupForm:ControlGroup;
constructor(formBuilder:FormBuilder){
this.signupForm=formBuilder.group({
name:['',Validators.required],
credentials:formBuilder.group({
username:['',Validators.required],
password:['',Validators.required]
})
});
}
}
Thisisthemostsimplisticwayofinstantiatingcontrols,whereeachcontrolisapropertynameofahashobjectwhosevalueisanarraywherethefirstelementisthedefaultvaluewewantforourcontrolsfollowedbytherangeofvalidatorswewanttoapplytoeachcontrol.
Thesethreewaysofrepresentingagroupofcontrolsastheblueprintforanimaginarysign-upformgiveusalotofflexibilitytocreatecomplexformsfromourcomponentcontroller.Whatsyntaxshouldyouchooseforyournextproject?Itdepends.WhilethislasttakeonhowtoinstantiateControlGroupandControlobjectsisprobablythemostpopularonebecauseofitssimplicity,theformergivesustheopportunitytorefertothecontrolsfromotherendsofourcontrollerclass,giventhefactthatsuchcontrolsareactuallypartofitsmembersAPI.Ultimately,itwilldependofwhereyouwantyourcontrolstobeandfromwheretheyareaccessible.
www.EBooksWorld.ir
ConnectingtheDOMandthecontrollerwithngFormModelSowecancreateformsimperativelyfromwithinourcomponentcontroller.Nowwhat?WeneedtolinkallthislogictoourHTMLtemplatesomehowandthisiswhereanewdirectivecomesintoplay:theNgFormModeldirective.ThisdirectivemustbeboundtoaDOMelement(usuallytheformoranyelementintervenedbyNgForm)andpopulatedwiththenameofaControlGroupobjectbinding.Fromthatmomentonwards,theControlobjectsattachedtotheControlGroupobjectarebondedtotheinputelementsflaggedwithangControldirectivematchingtheirnames.Thecodeisasfollows:
<form[ngFormModel]="signupForm"(submit)="doSomething($event)">
<div>
<inputtype="text"
placeholder="Yourname"
ngControl="name">
</div>
<divngControlGroup="credentials">
<inputtype="text"
placeholder="Yourusername"
ngControl="username">
<inputtype="password"
placeholder="Yourpassword"
ngControl="password">
</div>
<p>
<inputtype="submit"value="Signup">
</p>
</form>
Thereitis:ourbelovedsignupFormControlGroupisnowlinkedtoanactualformwhoseinputcontrols,handledbyngControldirectives,areautomaticallymappedtotheControlGroupcontrols.
NowitistimetotranslateallthistoourPomodoroproject,solet'spullupoursleevesasthere'ssomeworktodo.
www.EBooksWorld.ir
Arealexample–ourlogincomponentEarlierinthischapter,weimplementedabasicformtopublishourowntasks.Inanormalscenario,wewillnotbewillingtoleavethatfunctionalityopentoeveryone,sowewillwanttoprotectitwithapassword.Buildingaloginformisthefirststepandthatwewilldoalongthissectionbydevelopingournextfeature:login.
www.EBooksWorld.ir
TheloginfeaturecontextTheloginfunctionalitycanliveinparalleltotherestofapplicationfunctionalitiesandthusdeservesitsownfolderandfacade.ThewholeimplementationwilldependonacomponentnamedLoginComponent,solet'sstartbycreatingthebasicfileswerequireinsidethenewloginfeaturefolder,locatedatthesamelevelasshared,tasks,ortimer.First,wewillcreateacomponentwithnoimplementationanditsassociatedtemplate,plusthefacade.Donotworryabouttheimplementationdetailsnow.
Thecodeisasfollows:
app/login/login.component.ts–componentcontrollerclass
import{Component}from'@angular/core';
import{
FormBuilder,
ControlGroup,
Validators,
Control}from'@angular/common';
import{Router}from'@angular/router-deprecated';
@Component({
selector:'pomodoro-login',
templateUrl:'app/login/login.component.html'
})
exportdefaultclassLoginComponent{
loginForm:ControlGroup;
notValidCredentials:boolean=false;
constructor(
formBuilder:FormBuilder,
privaterouter:Router){}
authenticate(){}
}
app/login/login.component.html–componenttemplate
<form[ngFormModel]="loginForm"
class="container"
(ngSubmit)="authenticate()">
<h3>Thiscontentispassword-protected</h3>
<p>Pleaseenteryourcredentialsbelow</p>
<divclass="alertalert-danger"*ngIf="notValidCredentials">
Yourcredentialsarenotvalid!
</div>
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Yourusername"
www.EBooksWorld.ir
ngControl="username">
</div>
<divclass="form-group">
<inputtype="password"
class="form-control"
placeholder="Yourpassword"
ngControl="password">
</div>
<p>
<inputtype="submit"
class="btnbtn-success"
value="Authenticate"
[disabled]="!loginForm.valid">
</p>
</form>
app/login/login.ts–featurefacade
importLoginComponentfrom'./login.component';
export{
LoginComponent
};
Updatingtherouterconfigurationinourtoprootcomponentwillbeagreathelpintestingthechangeswewillconductoverthenextpages.Inordertodoso,let'seditdifferentpartsofthetoprootcomponent,asfollows:
app/app.component.ts–importstatementsblock
import{Component}from'@angular/core';
import{SHARED_PROVIDERS}from'./shared/shared';
import{HTTP_PROVIDERS}from'@angular/http';
import{ROUTER_PROVIDERS,RouteConfig,ROUTER_DIRECTIVES,Router}from
'@angular/router-deprecated';
import{TimerComponent}from'./timer/timer';
import{TasksComponent,TaskEditorComponent}from'./tasks/tasks';
import{FORM_PROVIDERS}from'@angular/common';
import{LoginComponent}from'./login/login';
...
app/app.component.ts–RouteConfigparams
...
@RouteConfig([
{path:'',
name:'Home',
redirectTo:['TasksComponent']
},{
path:'tasks',
name:'TasksComponent',
component:TasksComponent,
useAsDefault:true
www.EBooksWorld.ir
},{
path:'tasks/editor',
name:'TaskEditorComponent',
component:TaskEditorComponent
},{
path:'timer/...',
name:'TimerComponent',
component:TimerComponent
},{
path:'login',
name:'LoginComponent',
component:LoginComponent
}
])
exportdefaultclassAppComponent{}
www.EBooksWorld.ir
TheloginformtemplateItjusttakesaquickglanceattheformtounderstandthemachinerythatwillbebuiltinthecomponentcontrollerclasstobringthislittleguytolife.TheNgformModeldirectiveispointingtoaControlGroupnamedloginFormthatisyettobecreated.TheControlGroupwillwraptwoControlobjects(usernameandpassword),anditsstatewilldisabletheSubmitbuttonlocatedatthebottomoftheformwhennotvalid.Thecomponentwillfeatureamethodnamedauthenticate()thatwillhandletheformsubmitevent.Lastbutnotleast,wehavethislittlechunkofcoderightbeforeourinputcontrols:
<divclass="alertalert-danger"*ngIf="notValidCredentials">
Yourcredentialsarenotvalid!
</div>
Asyoumusthaveguessed,thismessagewillbedisplayedincasethecredentialsenteredarenotcorrect.Wewillperformthatvalidationintheimplementationoftheauthenticate()method,asyouwillseenext.Thereisanewdirectiveinourexample:thengSubmiteventdirective.InAngular'sownwords,itwillsignalwhentheusertriggersaformsubmission.Theuseofthiseventdirectivereplacesthe(submit)eventbindingwe'vebeenusingsofar.
www.EBooksWorld.ir
ThelogincomponentLet'sconducttheimplementationofthelogincomponentclassnow.Wecurrentlyhavethecontrollerclassskeleton,butweneedtobeefuptheconstructorandtheauthenticate()method:
app/login/login.component.ts
...
@Component({
selector:'pomodoro-login',
templateUrl:'app/login/login.component.html'
})
exportdefaultclassLoginComponent{
loginForm:ControlGroup;
notValidCredentials:boolean=false;
constructor(
formBuilder:FormBuilder,
privaterouter:Router){
this.loginForm=formBuilder.group({
username:['',Validators.required],
password:['',Validators.required]
});
}
authenticate(){
letcredentials:any=this.loginForm.value;
this.notValidCredentials=!this.loginForm.valid&&
this.loginForm.dirty;
if(credentials.username==='john.doe@mail.com'&&
credentials.password==='letmein')
{
this.router.navigateByUrl('/');
}else{
this.notValidCredentials=true;
}
}
}
Thispieceofcodeconformsprettymuchtowhatwealreadysawintheprevioussections.Wehaveskippedtheimportstatementblockinthecodesnippetforbrevitysake,butwecanclearlyseehowweinjecttheRouterandFormBuildertypeddependenciesintoourclass(alsoconfiguringtherouterobjectasaprivateclassmember)usingtheconstructorinjectionpatternfavoredbyAngular2.WithaninstanceoftheFormBuilderclassinplace,wecancreatetheControlGroupwerequire,assignittotheloginFormmember,andbinditlaterontotheNgFormModeldirectivedecoratingtheNgForm(thisis,theformelement,remember?)directiveawaitinginourtemplate.
FillingoutbothinputcontrolsisrequiredbythegraceoftheValidators.requiredstatic
www.EBooksWorld.ir
methodsfoundintheControlinstances,butthepieceofcodethatrequiresmoreattentionisprobablytheimplementationoftheauthenticate()method.Let'sdeconstructitbitbybit:
letcredentials:any=this.loginForm.value;
this.notValidCredentials=!this.loginForm.valid&&
this.loginForm.dirty;
Ourauthenticate()methodfirstbindsthevalueoftheloginFormcontrolgrouptothecredentialsvariable.RememberthatloginForm.valuewilltaketheshapeofahashobjectlikethis:
{username:"",password:""}
Ontheotherhand,thenotValidCredentialsclasspropertyevaluatestoabooleanvalueasaresultofthevalidityandstateoftheloginFormcontrolgroupstate.Thelastpieceofcodeismorestraightforwardandbasicallyentailscheckingifthecredentialsarevalidinwhichcasetheuserwillberedirectedtotheindexpage(feelfreetoreplacethedestinationpathtowhateveryoufeelismoreappropriate)orthewrongcredentialswarningwillbedisplayedonscreen.
Note
Weareevaluatingthecredentialsagainsthardcodedvaluesforthesakeofsimplicitybutyoushouldnever,andwewouldliketoremarkthewordneverinthisstatement,takethispathforcheckinglogininformationandgrantaccesstosensitivepartsofyourapplications.Thisiswaytooinsecureandcanbeeasilytamperedbymalicioushackerswithnoeffort.Wewillelaboratealittlebitonhandlingauthenticationinthefollowingpages,butwewillsticktohardcodedcredentialstominimizecomplexityinourexamples.InarealscenarioyoushouldalwaysrelyonaremotesecureauthenticationAPIandencryptedtokenstohandlesessionpersistence.Theseconcernsaremoregearedtowardsbackendprogrammingandareobviouslybeyondthescopeofthisbook.
www.EBooksWorld.ir
ApplyingcustomvalidationtoourcontrolsWehaveseenalreadyhowtoapplyvalidatorstoourcontrolsbyaddingvalidatormethodswheninstantiatingthem,butwestillneedtogiveanswertotworelevantconcerns:
Howtocreateourcustomvalidators?HowtocombinemorethanonevalidatorinasingleControlobject?
Thissectionwillcoverthesetwoissues.AcustomvalidatorisbasicallyafunctionthatexpectsaControlobjectinitssignatureandwillreturneitheranullvalue(iftheControlisvalid)orahashobjectcomprisedbykey/valuepairs.Wewantourloginformtocheckiftheusernameiscorrect.Sincetheusernameissupposedtobeanactuale-mailaddress,maybeitisagoodideatocheckiftheusernameinputcontainsavalide-mailaddressbeforelettingtheusermoveonwithsubmittingtheform.
Tip
Inanormalscenario,wewouldbeusingthepatternvalidatorinstead,butthepurposeofthisexampleistoshowcasethemechanicsofacustomvalidator.
So,let'smoveonandcreateane-mailvalidationfunctionandappendittothebodyofourLoginComponentclass.Thecodeisasfollows:
app/login/login.component.ts
privateemailValidator(control:Control):
{[key:string]:boolean}{
if(!/(.+)@(.+){2,}\.(.+){2,}/.test(control.value)){
return{
'emailNotValid':true
};
}
returnnull;
}
Theformofthereturningobjectwhenthecontrolvalueisnotvalidisnotsimple.ThatobjectwillbeappendedtotheerrorspropertyofthecontrolannotatedwithaValidatorfunction.Thisisquiteconvenientsincewecanthenprovidefurtherinsightsintothesourceoferrorwhenevaluatingvalidityoneachinput,mostlywhentheinputcontrolsfeaturemorethanonevalidationadapter.
Speakingofthedevil,nowweneedtoincludetwovalidatorsinourControlsotheusernameControlvalidatesitsvalueagainsttherequiredValidatorandournewcustomValidatorfunction.TheValidatorhasastaticmethodthatwilldothetrick:
this.loginForm=formBuilder.group({
username:['',
Validators.compose([
www.EBooksWorld.ir
Validators.required,
this.emailValidator
])
],
password:['',Validators.required]
});
www.EBooksWorld.ir
WatchingstatechangesinourcontrolsSofar,wesawhowwecanconductoperationsdependingoninputchangesinourcontrols,butitwouldbedefinitelynicetotakeamorereactiveapproachdependingoncertainuse-cases.ThegoodnewsisthatboththeControlGroupandtheControltypesexposetwoEventEmittermemberseach,whichwecansubscribeourownObserversto.Then,wewillgetpromptnotificationseverytimeanyControloritswrappingControlGroupobjectupdateseitheritsstatus(pristine,touchedandthelike)orvalue.WearetalkingaboutthestatusChangesandvalueChangesObservables,whicharepartofanyControlorControlGroupobject,andoperateexposingthesameinterfacewesawwhensubscribingtoHTTPobservablesinChapter6,AsynchronousDataServiceswithAngular2.
Let'sseeanactualexample.Itwouldbenicetodisplayareal-timevisualhintastheuserentershis/herusernameinformingifthedataenteredisanactualusernameornot.First,updateourcomponentcontrollertoincludeaBooleanfieldthatwillbeusedlaterontotoggleonandoffavisualnotificationinourtemplate.Thecodeisasfollows:
app/login/login.component.ts
exportdefaultclassLoginComponent{
loginForm:ControlGroup;
notValidCredentials:boolean=false;
showUsernameHint:boolean=false;
constructor(
formBuilder:FormBuilder,
privaterouter:Router){
this.loginForm=formBuilder.group({
username:['',Validators.compose([
Validators.required,
this.emailValidator])],
password:['',Validators.required]
});
constusername=this.loginForm.controls['username'];
username.valueChanges.subscribe(value=>{
this.showUsernameHint=(username.dirty&&
value.indexOf('@')<0);
});
}
//Restofcomponentclassremainsthesame
...
}
app/login/login.component.html
<divclass="form-group">
<inputtype="text"
class="form-control"
placeholder="Yourusername"
www.EBooksWorld.ir
ngControl="username">
<p*ngIf="showUsernameHint"class="help-block">
Thatdoesnotlooklikeaproperusername
</p>
</div>
PleasenoticehowwearereferringtotheusernameControlbytraversingthecontrolspropertyoftheloginFormobject.Withapointertotheusernameinplace,wecansubscribeobserverstoanyreal-timechangesinitsvalue,andthereforeactaccordingly.Inthiscase,weupdatethevalueoftheshowUsernameHintfield,ifthevalueenteredfulfilstheminimumrequirementsofausername.Whatevervalueittakes,avisualhintwillbedisplayedornotonscreen.
www.EBooksWorld.ir
MockingaclientauthenticationservicePerhapsthewordmocking,whichisprettycommoninthecontextofunittesting,isabitmisleadingherebutatleastservesasaheads-upforwhatwearegoingtobuildnow.Intheprevioussection,weimplementedaprettysimpleuserauthenticationcheckingbut,inarealscenario,weusuallydelegatealltheheavyliftingonanauthenticationservicethatwrapsallthenecessarytoolsforhandlinguserlogin,logout,andsometimesauthenticationforgrantingaccesstoprotectedareasofourapplication.
Next,wewillcreateasimplifiedversionofsuchserviceandwillputitinchargeofhandlinguserloginalongwiththecomponentwejustcreatedintheprevioussection.Thisservicewillalsomanageauthtokenpersistenceandprovidemethodstocheckiftheuserhasaccessgrantedtosecurepages.
Beforejumpingintothecode,let'ssummarizetheminimumrequirementsthisservicemustfulfil:
WeneeditsAPItoexposeamethodtohandleuserloginUserlogoutmustbehandledaswellbyapublicmethodinthisAPIAthirdmethodorpropertyshouldinformiftheuserisloggedinornotsotheycanproceedtosecuredpagesHavinganobservablepropertyinformingofthecurrentstateoftheactiveuserforauthenticationwillbecomehandytomaketheoverallUImorereactive
Withthesespecificationsinmind,let'sbuildouridealauthenticationservice.Sincethisserviceiscomponent-agnosticandwillhaveanimpactonthewholeapplication,wewillstoreitintheservicesfolderofoursharedcontext,applyingthenamingconventionswealreadyknowandexposingitthroughthesharedfacade:
app/shared/services/authentication.service.ts
import{Injectable,EventEmitter}from'@angular/core';
@Injectable()
exportdefaultclassAuthenticationService{
constructor(){}
login({username,password}):Promise<boolean>{}
logout():Promise<boolean>{}
staticisAuthorized():boolean{}
}
app/shared/shared.ts
importQueueablefrom'./interfaces/queueable';
www.EBooksWorld.ir
importTaskfrom'./interfaces/task';
importFormattedTimePipefrom'./pipes/formatted-time.pipe';
importQueuedOnlyPipefrom'./pipes/queued-only.pipe';
importAuthenticationServicefrom'./services/authentication.service';
importSettingsServicefrom'./services/settings.service';
importTaskServicefrom'./services/task.service';
constSHARED_PIPES:any[]=[
FormattedTimePipe,
QueuedOnlyPipe
];
constSHARED_PROVIDERS:any[]=[
AuthenticationService,
SettingsService,
TaskService
];
export{
Queueable,
Task,
FormattedTimePipe,
QueuedOnlyPipe,
SHARED_PIPES,
AuthenticationService,
SettingsService,
TaskService,
SHARED_PROVIDERS
};
Asyoucanseeintheresultingfacade,thenewservicewillbecomepartoftheSHARED_PROVIDERSgrouptoken.Then,itwillbeavailableforourapplicationinjector,sincethissymbolisbeingdeclaredintheprovidersarrayofourrootcomponent.
Backtotheserviceclass,weimportedtheInjectabledecorator.Asyouknow,wewillneeditifwewantourAuthServiceclasstobeautomaticallyinstantiatedandinjectedasasingletoninourcomponentsbyAngular(incaseourclassrequiresitsowndependenciesinthefuture).WealsoimporttheEventEmitterclass,whichwewillcoverlaterinthissection.
InthebodyoftheAuthenticationServiceclass,wehavedefinedanemptyconstructorandthreemethodswithnoimplementation(oneofthembeingstatic).Whilethenamesgiveaverygoodhintofthepurposeofeachmethod,perhapsthelastonerequiressomemoreelaboration:TheisAuthorized()methodwillinformiftheuserhaspermissionstoaccesssecuredpages.ThereasonwhyitisstaticisbecausewewillneedtouseitinsomeareaswhereAngular'sdependencyinjectionmachinerycannotreachsonoautomaticproviderinjectionisavailable.
Ourfirstrequirementwastoprovideapublicmethodtohandleuserlogin.Let'sgoforit.Get
www.EBooksWorld.ir
backtotheAuthenticationServicemoduleandextendtheloginmethodwiththefollowingimplementation:
app/shared/services/authentication.service.ts
login({username,password}):Promise<boolean>{
returnnewPromise(resolve=>{
letvalidCredentials:boolean=false;
//@NOTE:Inarealscenariothischeck
//shouldbeperformedagainstawebservice:
if(username==='john.doe@mail.com'&&
password==='letmein'){
validCredentials=true;
window.sessionStorage.setItem('token','eyJhbGciOi');
}
resolve(validCredentials);
});
}
Asyoucanseefromthecommentsinlineinthecode,wearenotsubmittingdataforvalidationtoaremotewebservicealthoughweshoulddefinitelydo.Pleaserecallthewarningweraisedinpreviouschapters:youshouldneverimplementuservalidationthisway.Havingsaidthat,let'sreviewthisimplementation.Inthefirstplace,thereissomethingthatdrawsourattention:thereturningtype.ThismethodissupposedtoreturnaPromiseandthereisagoodreasonforthat.Usually,youwouldalsowanttoimplementanasyncHTTPconnectiontoaremoteservicesoyoucansendtheusercredentialsandwaitforaresponse.Hence,weusetheasynchronousinterfaceintheformofareturningPromise,whichresolvestoaBooleanvalueinformingifthecredentialsprovidedaregoodtoaccessthesystemornot.Ontheotherhand,themethodsignatureisnotanannotatedargument,butadeconstructedobjectinformingthatthismethodwillexpectanytypeorobjectinitspayloadcontainingbothusernameandpasswordproperties.Lastbutnotleast,rightafterconductingourfakeuservalidation,westorearandomtokenontotheuser'sbrowserusingthebrowser'sownsessionstoragelayer.Thisisacommonwayofhandlingauthenticationandusersessionpersistencenowadays,withthesoledifferencethatthetokenisusuallysentinthebodyoftheserverresponseandthereafterissentbackintherequestheadersoneveryinformationrequestmadetotheserver.
Conductingaserver-sideimplementationisbeyondthescopeofthisbook,sowewillnotexplorethattopicingreaterdepth.YoucanrefertothePacktlibraryforfurtherreference.
Nowthatweknowhowtohandleuserlogin,implementingauserlogout()methodthatliterallyreverseswhatthepreviouslogin()methoddidisprettyeasy:
app/shared/services/authentication.service.ts
logout():Promise<boolean>{
returnnewPromise(resolve=>{
window.sessionStorage.removeItem('token');
www.EBooksWorld.ir
resolve(true);
});
}
Ourthirdrequirementwastoprovideapropertyormethodthatwouldtellusiftheuserisauthenticated,sotheycanproceedtosecuredpages.Keepinginmindthatuserpermissionistiedtotheexistenceorabsenceofasecuritytokenstoredinthebrowserstoragelayer,themethodlogicisreallysimple:
staticisAuthorized():boolean{
return!!window.sessionStorage.getItem('token');
}
Wewilldiscussthismethodandtherationalebehinditsstaticannotationlateron.Now,let'smoveintothelastbitofourservice:providinganObservablethatallowsUIelementsandotherapplicationclientstosubscribetoupdatesintheuserstatus.First,wewillcreateapublicEventEmittermember,whichwecanusetosendnotificationseverytimetheuserlogsinandoutsothatotherclassesandcomponentscansubscribetoitasmereobserversandreacttothoseevents.Obviously,theloginandlogoutmethodswillbeupdatedtoalsosendthecorrespondingnotificationstotheobserversdependingontheactionstakenandtheuserstateatalltimes.
Withallthesechanges,thisisthefinallayoutofourinjectableauthenticationservice:
import{Injectable,EventEmitter}from'@angular/core';
@Injectable()
exportdefaultclassAuthenticationService{
userIsloggedIn:EventEmitter<boolean>;
constructor(){
this.userIsloggedIn=newEventEmitter();
}
login({username,password}):Promise<boolean>{
returnnewPromise(resolve=>{
letvalidCredentials:boolean=false;
//@NOTE:Inanormalscenariothischeck
//shouldbeperformedagainstawebservice:
if(username==='john.doe@mail.com'&&
password==='letmein'){
validCredentials=true;
window.sessionStorage.setItem('token','eyJhbGciOi');
}
this.userIsloggedIn.emit(validCredentials);
resolve(validCredentials);
});
}
logout():Promise<boolean>{
returnnewPromise(resolve=>{
www.EBooksWorld.ir
window.sessionStorage.removeItem('token');
this.userIsloggedIn.emit(false);
resolve(true);
});
}
staticisAuthorized():boolean{
return!!window.sessionStorage.getItem('token');
}
}
www.EBooksWorld.ir
ExposingournewservicetoothercomponentsWiththeauthenticationserviceprovidernowavailablefromourapplicationinjector,wecanbeginhookingitupinothercomponents:theloginfeatureisthemostlogicalstartingpoint.First,opentheLoginComponentcodeunitandimportthenewAuthenticationServicetokensothatwecanproperlyuseitstypetoinjectitintothecomponent:
app/login/login.component.ts
import{Component}from'@angular/core';
import{
FormBuilder,
ControlGroup,
Validators,
Control}from'@angular/common';
import{Router}from'@angular/router-deprecated';
import{AuthenticationService}from'../shared/shared';
...
Inthesamecodeunit,let'snowupdatetheconstructorpayloadwithanewargumentannotatedwiththeAuthenticationServicetoken,sotheAngular2DImachinerybecomesawarethatthismodulerequiresthattypetobeinjected:
constructor(
formBuilder:FormBuilder,
privaterouter:Router,
privateauthService:AuthService){
//Restofconstructorimplementationremainsunchanged
...
}
Withallthecodeinplace,nowwecanreplaceourauthenticate()methodtoremovethebusinesslogic:
authenticate(){
letcredentials:any=this.loginForm.value;
this.notValidCredentials=!this.loginForm.valid&&
this.loginForm.dirty;
this.authenticationService.login(credentials).then(success=>{
if(success){
this.router.navigateByUrl('/');
}else{
this.notValidCredentials=true;
}
});
}
www.EBooksWorld.ir
BlockingunauthorizedaccessWithallthisinplace,it'stimetoactuallypreventunloggedusersfromaccessingprotectedcontent.Inourcase,itjustentailsprotectingthetaskeditingformcomponentfromunauthorizedrequests.Inthepreviouschapter,wesawhowtoalloworpreventcomponentinstantiationbymeansofRouterhooks.
Withthatknowledgetohand,protectingthetaskeditorcomponentfromundesiredvisitsbecomesquitesimple.OpentheTaskEditorComponentfile,importournewAuthenticationServiceprovider,andcheckwhethertheuserisauthorizedbybindingtheexecutionofthestaticisAuthorized()methodtotheCanActivatedecorator:
app/tasks/task-editor.component.ts
...
//Otherimportstatementsremainastheyarealready
import{
Task,
TaskService,
AuthenticationService}from'../shared/shared';
@Component({
selector:'pomodoro-tasks-editor',
directives:[ROUTER_DIRECTIVES],
providers:[Title],
templateUrl:'app/tasks/task-editor.component.html',
styles:[`
.ng-valid{border-color:#3c763d;}
.ng-invalid{border-color:#a94442;}
.ng-untouched{border-color:#999999;}
`]
})
@CanActivate(AuthService.isAuthorized)
exportdefaultclassTaskEditorComponentimplementsOnActivate,CanDeactivate,
OnDeactivate{
//Theclassimplementationremainsthesame
...
}
Andthat'sit!Now,anyunloggeduserattemptingtoaccesstheprotectedtaskeditorcomponentwillgetnothing!Ifyoulookcarefullyatthe@CanActivate()decorator,youwillunderstandwhywedefinedtheisAuthorized()methodoftheAuthenticationServiceasstatic.Thereasonreliesonthefactthatwecanonlyinjectdependencysingletonsinourcomponentsbutnotindecorators.Whilethisisnotexactlytrue(wecanleveragetheprovide()injectortobringthedesiredsingletonalthoughthecoderequiredisnowherenearassimpleorneat),thetruthisthatthisimplementationissimple:providingthesamelevelofeffectivenessinaneatandclearfashion.
Note
www.EBooksWorld.ir
TheidealscenariowouldbetoinjectboththeAuthenticationServiceandtheRouterprovidersintheCanActivateimplementation,andthenredirecttheusertotheloginpageshouldtheuserisnotloggedin.
Unfortunately,atthetimeofwrappingupthewritingofthisbook,thereisstillnoformalsupportfordependencyinjectioninthecontextoftheCanActivaterouterhook.However,thisissueispartofthefeaturesthatwillbecomepartofAngular2Final.Itisquitelikelythatthe@CanActivatedecoratorwillbereplacedbyananalogueinstancemethodofaparentroutingcomponentonceAngular2becomesfinaleventually.Pleaserefertotheofficialdocumentation.
www.EBooksWorld.ir
MakingtheUIreactivetotheuserauthenticationstatusAllright,sounauthorizeduserscannotaccessthetaskeditorformcomponent.However,havinganunresponsivelinkinourmaintoolbarisdefinitelynotgood,soweshouldleveragetheObservablefeaturesoftheAuthenticationServicetofliptheUIwheneverthereisachangeintheuserloginstatus.
Rightnow,thenavbarfeaturestheLoginlinkthatleadstheuserloginformpage.WhatwewanttodoistohidethePublishTasklinkandmakesureweonlydisplayitwhentheuserisloggedin,nomatterwhereandhowthisloginprocedurewasundertaken.Ontheotherhand,wealsowanttooffertheenduseraLogoutlinkwhenloggedin,sotheycanshutdownhissessioninconfidence.Thislogoutlinkshouldbemadeavailableforloggedinusersonly.AccesstotheprotectedcomponentbyhardcodingURLsisnotaconcern,sincethe@CanActivatedecoratorwilldoitsjobtokeepundesiredusersaway.
Nowthatwehavedescribedtherequirements,let'sputthemintopractice.Openthetoprootcomponentfileandupdateitsimplementation(itremainedemptyuntilnow).WewillneedtheAuthenticationServiceandtheRouterdependenciestodoso,somakesuretoimportthematthetopofthefile:
app/app.component.ts
import{Component}from'@angular/core';
import{
SHARED_PROVIDERS,
AuthenticationService}from'./shared/shared';
import{HTTP_PROVIDERS}from'@angular/http';
import{
ROUTER_PROVIDERS,
RouteConfig,
ROUTER_DIRECTIVES,
Router}from'@angular/router-deprecated';
//Restofimportstatementsremainthesame
…
Withthetokensproperlydeclaredintheimportstatements,wecanmoveonandprovideanimplementationfortheAppComponentclass:
app/app.component.ts
...
exportdefaultclassAppComponent{
userIsLoggedIn:boolean;
constructor(
privateauthenticationService:AuthenticationService,
privaterouter:Router){
authenticationService.userIsloggedIn.subscribe(isLoggedIn=>{
this.userIsLoggedIn=isLoggedIn;
});
www.EBooksWorld.ir
}
logout($event):void{
$event.preventDefault();
this.authenticationService.logout().then(success=>{
if(success){
this.router.navigateByUrl('/');
}
});
}
}
WehavedeclaredauserIsLoggedInBooleanfield,whichwillchangeitsvalueeverytimetheobservableuserIsloggedInmemberoftheinjectedAuthenticationServicetypechangesitsvalue.WealsoinjectedtheRoutertypeandcreatedanewcomponentmethodnamedlogout()thatwillwipeouttheusersessionandredirecttheusertotherootpageuponsigningoutfromtheapplication.
ThisgivesusthechancetowrapuptheapplicationUIbyupdatingtherootcomponenttemplatetomakethesensiblelinksfullyreactivetothesechanges:
app/app.component.html
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroApp</strong>
</div>
<ulclass="navnavbar-navnavbar-right">
<li><a[routerLink]="['TasksComponent']">Tasks</a></li>
<li><a[routerLink]="['TimerComponent']">Timer</a></li>
<li*ngIf="userIsLoggedIn">
<a[routerLink]="['TaskEditorComponent']">PublishTask</a>
</li>
<li*ngIf="!userIsLoggedIn"><a[routerLink]="
['LoginComponent']">Login</a>
</li>
<li*ngIf="userIsLoggedIn">
<ahref="#"(click)="logout($event)">Logout</a>
</li>
</ul>
</div>
</nav>
<router-outlet></router-outlet>
Giveitatry!Reloadtheapplication,checkthelinksavailableatthenavbar,headovertotheloginpage,proceedtologinwiththecredentials,andcheckthenavbaragain...Magic!
www.EBooksWorld.ir
RunningtheextramileonaccessmanagementApparently,wehaveeverythingthatittakestomoveonwithourapplication.However,asourapplicationgrowsandmoreareasneedtobeprotected,wewillfindourselvesfacingtheburdenoftogglingvisibilityonmoreandmorelinksandhavingtoenableaccessandactivationofcomponentsonebyonebyimplementingthe@CanActivatedecoratoroneach.
Obviously,thisscalesupjustbadly,soitwouldbegreattorelyonaone-size-fits-allsolutioninstead.Unfortunately,atthetimeofwriting,theAngular2frameworkstilldoesnotprovideafeasiblesolutiontotacklewiththisconcern.Ontheotherhand,andaccordingtomodernUX,mostofthetimetheexpectedbehavioristoprovidetheuserwithasmanybrowsingalternativesaspossibleandredirecttheusertotheloginpageonlywhererequired.
Inthislastsection,wewillintroduceagenericworkaroundforthis,basedonthefollowingcriterion:protectingareasofcontentasawholebywrappingtheminsidechildroutesthatwillredirecttheusertotheloginpagewheneverunauthorizedaccessisdetected,whereparameterssuchasthelocationoftheloginpatharefullyconfigurablefromoursolution.
www.EBooksWorld.ir
BuildingourownsecureRouterOutletdirectiveOurworkaroundisbasedondevelopingourveryownRouterOutletdirectivebyextendingtheRouterOutletclassbakedinAngular2,whichwillbeslightlyrewrittentooverridethebasedirective'sdefaultbehaviorwhenitcomestoproceedingtoactivate(ornot)therequestedcomponent.Allofthisbasedonthecurrentuserloginstatus.
Todoso,wewillcreateanewdirectiveinoursharedcontext,whichwillbeusedacrosstheapplication.Soweneedtoexposeitinthesharedfacadeaswell.
app/shared/directives/router-outlet.directive.ts
import{
Directive,
ViewContainerRef,
DynamicComponentLoader,
Attribute,
Input}from'@angular/core';
import{
Router,
RouterOutlet,
ComponentInstruction}from'@angular/router-deprecated';
import{AuthenticationService}from'../shared';
@Directive({
selector:'pomodoro-router-outlet'
})
exportdefaultclassRouterOutletDirectiveextendsRouterOutlet{
parentRouter:Router;
@Input()protectedPath:string;
@Input()loginUrl:string;
constructor(
_viewContainerRef:ViewContainerRef,
_loader:DynamicComponentLoader,
_parentRouter:Router,
@Attribute('name')nameAttr:string){
super(_viewContainerRef,_loader,_parentRouter,nameAttr);
this.parentRouter=_parentRouter;
}
activate(nextInstruction:ComponentInstruction):Promise<any>{
letrequiresAuthentication=
this.protectedPath===nextInstruction.urlPath;
if(requiresAuthentication&&
!AuthenticationService.isAuthorized()){
this.parentRouter.navigateByUrl(this.loginUrl);
}
returnsuper.activate(nextInstruction);
}
www.EBooksWorld.ir
}
Here,wearecreatinganewdirectivethatextendsfromtheRouterOutletdirectivewehavebeenusingallthistimeinourapplication.Thisdirectiveneedstobemadeavailableforusefromtheotherfeaturecontextsofourapplication:
app/shared/shared.ts
importQueueablefrom'./interfaces/queueable';
importTaskfrom'./interfaces/task';
importFormattedTimePipefrom'./pipes/formatted-time.pipe';
importQueuedOnlyPipefrom'./pipes/queued-only.pipe';
importAuthenticationServicefrom'./services/authentication.service';
importSettingsServicefrom'./services/settings.service';
importTaskServicefrom'./services/task.service';
importRouterOutletDirectivefrom'./directives/router-outlet.directive';
constSHARED_PIPES:any[]=[
FormattedTimePipe,
QueuedOnlyPipe
];
constSHARED_PROVIDERS:any[]=[
AuthenticationService,
SettingsService,
TaskService
];
constSHARED_DIRECTIVES:any[]=[
RouterOutletDirective
];
export{
Queueable,
Task,
FormattedTimePipe,
QueuedOnlyPipe,
SHARED_PIPES,
AuthenticationService,
SettingsService,
TaskService,
SHARED_PROVIDERS,
RouterOutletDirective,
SHARED_DIRECTIVES
};
Backtothedirectivefile,wecanseeitinheritsthesameconstructoroftheinheritedRouterOutletconstructor.Thus,wewillimportthesamerequiredtokenssothatwecan
www.EBooksWorld.ir
properlydeclaretheconstructordependenciesandcallthesuperclassconstructorwithsuper().Thecodeisasfollows:
app/shared/directives/router-outlet.directive.ts
constructor(
_elementRef:ElementRef,
_loader:DynamicComponentLoader,
_parentRouter:Router,
@Attribute('name')nameAttr:string){
super(_elementRef,_loader,_parentRouter,nameAttr);
this.parentRouter=_parentRouter;
}
Intheconstructorbody,wedonotonlyinjectthedependencies,wealsoassigntheRouterinstanceinaclassmemberforfutureuse.Thecoreofthesolutionreliesonoverridingtheimplementationofthenativeactivate()method,wherewebasicallyintroduceanauthenticationcheck(thatiswhywealsoimporttheAuthenticationServiceatthetopofthescript)toseeiftherecentlyrequiredcomponentlivesinthedomainoftheprotectedpath.Inthatcase,wewillredirecttheusertotheloginpagelocationshouldtheauthenticationtokenisnotavailablethankstothestaticmethodisAuthorized()exposedbytheAuthenticationService.Inanyevent,themethodwillfinallyreturntheresultofthesuperclass'activatemethod,representedbyaPromiseobject.Thecodeisasfollows:
activate(nextInstruction:ComponentInstruction):Promise<any>{
letrequiresAuthentication=this.protectedPath===nextInstruction.urlPath;
if(requiresAuthentication&&
!AuthenticationService.isAuthorized()){
this.parentRouter.navigateByUrl(this.loginUrl);
}
returnsuper.activate(nextInstruction);
}
WheredowefetchtheseprotectedPathandloginUrlparameters?Asyousawalreadyatthebeginningofthissection,weareexposingtwoinputparametersonthisdirective,whichwillmakeitsinstanceslooklikethis:
<pomodoro-router-outletprotectedPath="edit"loginUrl="/login">
</pomodoro-router-outlet>
So,openupthetoproutercomponentfileandreplacethecurrentRouterOutletdirectiveinstanceinthetemplatewithafewlinesofcode,rightafterimportingtheSHARED_DIRECTIVESsymbolinthedirectivespropertyofthecomponentdecorator:
app/app.component.ts
import{Component}from'angular2/core';
import{
www.EBooksWorld.ir
SHARED_PROVIDERS,
AuthenticationService,
SHARED_DIRECTIVES}from'./shared/shared';
...
@Component({
selector:'pomodoro-app',
directives:[ROUTER_DIRECTIVES,SHARED_DIRECTIVES],
...
})
app/app.component.html
<navclass="navbarnavbar-defaultnavbar-static-top">
<divclass="container">
<divclass="navbar-header">
<strongclass="navbar-brand">MyPomodoroApp</strong>
</div>
<ulclass="navnavbar-navnavbar-right">
<li><a[routerLink]="['TasksComponent']">Tasks</a></li>
<li><a[routerLink]="['TimerComponent']">Timer</a></li>
<li*ngIf="userIsLoggedIn">
<a[routerLink]="['TaskEditorComponent']">
PublishTask
</a>
</li>
<li*ngIf="!userIsLoggedIn">
<a[routerLink]="['LoginComponent']">Login</a>
</li>
<li*ngIf="userIsLoggedIn">
<ahref="#"(click)="logout($event)">Logout</a>
</li>
</ul>
</div>
</nav>
<pomodoro-router-outlet
protectedPath="tasks/editor"
loginUrl="login">
</pomodoro-router-outlet>
Lastbutnotleast,inordertotryoutthissolution,westillneedtodotwothings:remove(orcommentout)the@CanActivate()decoratorinthetaskeditorcomponentandthentweaktherouterCanDeactivate()routerhooktolooklikethis:
app/tasks/task-editor.component.ts
routerCanDeactivate(
next:ComponentInstruction,
prev:ComponentInstruction){
return!AuthenticationService.isAuthorized()||
this.changesSaved||
confirm('Areyousureyouwanttoleave?');
}
Thisway,wecansmoothlydeactivatethecomponentiftheuserisnotloggedin.Whyshould
www.EBooksWorld.ir
wedothis?Basically,thisistheflowtheuserwillfollow:
TheuserasksforaprotectedcomponentTherouterdirectivechecksiftheuserisauthenticatedIfloggedinalready,thedirectivedoesnothingandthecomponentisactivatedIfnot,althoughthecomponentwillbeactivated,aredirectionwillbeperformedatthesametimeandtheuserwilllandsafelyonthecomfortoftheloginpage
Inordertoproperlytryoutthissolution,removetheconditionalfromtheroutesetting.Wewanttoactuallydisplaythelinkbutredirecttheuserifnotloggedin.Thecodeisasfollows:
app/app.component.html
...
<ulclass="navnavbar-navnavbar-right">
<li><a[routerLink]="['TasksComponent']">Tasks</a></li>
<li><a[routerLink]="['TimerComponent']">Timer</a></li>
<li>
<a[routerLink]="['TaskEditorComponent']">
PublishTask
</a>
</li>
<li*ngIf="!userIsLoggedIn">
<a[routerLink]="['LoginComponent']">Login</a>
</li>
<li*ngIf="userIsLoggedIn">
<ahref="#"(click)="logout($event)">Logout</a>
</li>
</ul>
...
Note
Thereisanimportantcaveatinthissolution:theprotectedcomponentwillbeactuallyrenderedonscreenbeforebouncingtheusertotheloginpage(wheneverloginisrequired).Thisisdefinitelynotgood,sincethereisachancethattheprotectedcontentswillflickeronscreenifthesecurecomponentinstantiationtakeslongerthanexpectedoreitherthecanDeactivate()methodposessomeconditionsinordertomoveon(hencethechangeweintroduced.
Otherwise,thetransitionwillbesofastthatthechancesarethattheenduserwillnotevennoticethesecomponentshavebeeninstantiated.Inanyevent,usethiswithcareandwatchoutfortheCanDeactivatefunctioninallyourimplementations.
www.EBooksWorld.ir
SummaryThiswasquitealonganddensecomplicatedchapter,withoutanydoubt.ThemostimportanttakeawayfromthischapterwasthatthereisnotonebutmanyalternativeswhenitcomestodesigningandimplementingformsinAngular2.Someofthemaremoredeclarativeandsomeothersaremodeimperative.Whenshouldyouuseoneorfavoranother?Aswesaidalready,itdependsonwhereandhowyouwanttoaccessandtackletheissueofaccessingtheControlandControlGroupstateandvalidityproperties.
Userauthenticationwasalsocoveredinthischapterandweintroduceddifferentalternativesforcateringwithprotectedareasofoursite.StaytunedandkeepaneyeonthelatestaccomplishmentsmadeintheAngular2arena,sinceitisquitelikelythatnewworkaroundsforthemostcommonusecaseswillspringoutdownthetrack.
Now,getreadytoconfrontthelastlegofourjourneyintoAngular2,wherewewillprovidesomecoverageontheframeworksupportforanimationsbeforewrappingupeverythingbylearningsomeunittestingtechniques—allinthelastchapterofthebook.
www.EBooksWorld.ir
Chapter9.AnimatingComponentswithAngular2Nowadays,animationsareoneofthecornerstonesofmodernuserexperiencedesign.FarfromjustrepresentingavisualeyecandyforbeautifyingtheUI,theyhavebecomeanimportantpartofthevisualnarrative.Animationspavetheroadtoconveymessagesinanon-intrusiveway,becomingacheapbutpowerfultoolforinformingtheuserabouttheunderlyingprocessesandeventsthathappenwhileweinteractwithourapplication.Themomentananimationpatternbecomeswidespreadandtheaudienceembracesitasamodernstandard,wegainaccesstoapricelesstoolforenhancingourapplication'suserexperience.Animationsarelanguage-agnostic,arenotnecessarilyboundtoasingledeviceorenvironment(web,desktopormobile)andarepleasanttotheeyeofthebeholderwhenusedwisely.Inotherwords,animationsareheretostayandAngular2hasastrongcommitmenttothisaspectofmodernvisualdevelopment.
WithallmodernbrowsersembracingthenewerfeaturesofCSS3foranimationhandling,Angular2offerssupportforimplementingimperativeanimationscriptingthroughanincrediblyeasybutpowerfulAPI.Thischapterwillcoverseveralapproachestoimplementinganimationeffects,movingfromleveragingplainvanillaCSSforapplyingclass-basedanimations,toimplementingscriptroutineswhereAngular2takesfullresponsibilityforhandlingDOMtransitions.
Inthischapterwewill:
CreateanimationswithplainvanillaCSSLeverageclass-namedanimationwiththengClassdirectivetobetterhandletransitionsLookatAngular'sbuilt-inCSShooksfordefiningstylesforeachtransitionstateAnimatecomponentswiththeCssAnimationBuilderAPIDesigndirectivesthathandleanimationIntroducengAnimate2.0
Note
Asawordofcaution,bearinmindthatthecurrentimplementationofanimationinAngular2atthetimeofwritingis,toacertainextent,temporary.Inthatsense,allthefunctionalitiesdescribedinthischaptercouldprobablybedeprecatedinthelongrunastheAngular2codebasematures.However,fornow,thereisnoreasontoholdourselvesbackandleveragetheanimationmodulesalreadyavailableintheframework.Thiswillgiveusthepowerandfunctionalityrequiredtoenhancetheoveralluserexperienceofourapplications.
www.EBooksWorld.ir
CreatinganimationswithplainvanillaCSSTheinceptionofCSS-basedanimationsetanimportantmilestoneinmodernwebdesign.Beforethat,weusedtorelyonJavaScripttoaccommodateanimationsinourwebapplicationsbymanipulatingDOMelementsthroughcomplexandcumbersomescriptsbasedonintervals,timeouts,andloopsofallsorts.Unfortunately,thiswasneithermaintainablenorscalable.
ThenmodernbrowsersembracedthefunctionalitiesbroughtbytherecentCSStransform,transition,keyframes,andanimationproperties.Thisbecameagamechangerinthecontextofwebinteractiondesigninrecenttimes.WhilesupportforthesetechniquesinbrowserssuchasMicrosoftInternetExplorerisfarfromoptimal,therestofthebrowsersinstore(includingMicrosoft'sveryownEdge)providefullsupportfortheseCSSAPIs.
Note
MSIEprovidessupportfortheseanimationtechniquesonlyasofVersion10.
WeassumethatyouhaveabroadunderstandingofhowCSSanimationworksinregardsofbuildingkeyframe-drivenortransition-basedanimations,sinceprovidingcoveragetothesetechniquesisobviouslyoutofthescopeofthisbook.Asarecap,wecanhighlightthefactthatCSS-basedanimationisusuallyimplementedbyanyoftheseapproaches,orevenacombinationofboth:
Transitionproperties,thatwillactasObserversofeitherallorjustasubsetoftheCSSpropertiesappliedtotheDOMelementsimpactedbytheselector.WheneveranyoftheseCSSpropertiesischanged,theDOMelementwillnottakethenewvaluerightaway,butwillexperienceasteadytransitionintoitsnewstate.Namedkeyframe,animations,wherewedefinedifferentstepsoftheevolutionofoneorseveralCSSpropertiesunderauniquename,whichwillpopulatelateronananimationpropertyofagivenselector,beingoneabletosetadditionalparametersasthedelay,durationoftheanimationtweeningorthenumberofiterationsthatsuchanimationismeanttofeature.
Aswecanseeinthetwoaforementionedscenarios,theuseofaCSSselectorpopulatedwithanimationsettingsisthestartingpointforallthingsrelatedtoanimation,andthatiswhatwewilldonow:let'sbuildafancypulseanimationtoemulateaheartbeat-styleeffectinthebitmapthatdecoratesourPomodorotimer.
Wewilluseakeyframe-basedanimationthistime,sowewillbeginbybuildingtheactualCSSroutineinaseparatestylesheet.Theentireanimationisbasedonasimpleinterpolationwherewetakeanobject,scaleitupby10percentandscaleitbackdownagaintoitsinitialstate.Thiskeyframe-basedtweeningisthennamedandwrappedinaCSSclassnamedpulse,whichwillexecutesuchanimationinaninfiniteloopwhereeachiterationtakes1secondtocomplete.
www.EBooksWorld.ir
AlltheCSSrulesforimplementingthisanimationwillliveinanexternalstylesheetpartofthetimerwidgetcomponent,withinthetimerfeaturefolder:
app/timer/timer-widget.component.css
@keyframespulse{
0%{
transform:scale3d(1,1,1);
}
50%{
transform:scale3d(1.1,1.1,1.1);
}
100%{
transform:scale3d(1,1,1);
}
}
.pulse{
animation:pulse1sinfinite;
}
Asforthispointon,anyDOMelement(intheTimerWidgetComponenttemplate)annotatedwiththisclassnamewillvisuallybeatlikeaheart.Thisvisualeffectisactuallyagoodhintthattheelementisundertakingsomekindofaction,soapplyingittothemainpomodoroiconbitmapinourpomodorotimerwidgetwhenthecountdownisonwillhelpconveythefeelingthatanactivityiscurrentlytakingplaceinalivelyfashion.
Thankfully,wehaveagoodwaytoapplysucheffectonlywhenthecountdownisactive.WeusetheisPausedbindingintheTimerWidgetComponenttemplate.BindingitsvaluetotheNgClassdirectiveinordertorendertheclassnameonlywhenthecomponentisnotpausedwilldothetrick,sojustopenthetimerwidgetcodeunitfileandaddareferencetothestylesheetwejustcreatedandapplythedirectiveasdescribedpreviously:
app/timer/timer-widget.component.ts
...
@Component({
selector:'pomodoro-timer-widget',
styleUrls:['app/timer/timer-widget.component.css'],
template:`
<divclass="text-center">
<imgsrc="/app/shared/assets/img/pomodoro.png"
[ngClass]="{pulse:!isPaused}">
<h3><small>{{taskName}}</small></h3>
<h1>{{minutes}}:{{seconds|number:'2.0'}}</h1>
<p>
<button(click)="togglePause()"class="btnbtn-danger">
{{buttonLabelKey|i18nSelect:buttonLabelsMap}}
</button>
</p>
www.EBooksWorld.ir
</div>`
})
...
Andthat'sit!RunourpomodoroappandclickontheTimerlinkatthetoptoreachthetimercomponentpageandcheckthevisualeffectliveafterstartingthecountdown.Stopitandresumeitagaintoseetheeffectappliedonlywhenthecountdownisactive.
www.EBooksWorld.ir
HandlinganimationwithCSSclasshooksAswehavejustseenintheprevioussection,applyingvisualeffectsbasedonCSSclassesisabreezethanksmostlytotheflexibilitywehaveforaddingcustomclassnamesinAngular2.
Ontopofthat,Angularprovidessupportforanimationclasshooks,afunctionalitythatwasalreadyavailableinAngular1.x,underadifferentincarnationthough.Basically,themechanicsisasfollows:DOMelementsmanagedbytemplate-drivendirectives(NgSwitch,NgFor,orNgIf)canbedecoratedwiththeng-animateclassname.Fromthatverymoment,suchelementswillfeatureadditionalclassnamesdependingonthestageoftheanimationsappliedtothatDOMelementinthecontextofthewrappingcomponentlifecycle.
Thislaststatementmightsoundabitoddanddaunting,solet'sseeallthisinactionthroughanactualexample.OpenourTasksComponenttemplateandupdatetherowtagofthetaskslistbyaddingaclassnamedng-animatetothemarkup.Then,dothesamewiththequeuedlabeldisplayedwhenthetaskmodelislinedupinourtasksqueue.Thecodeisasfollows:
app/tasks/tasks.component.html
<tr*ngFor="lettaskoftasks;leti=index"class="ng-animate">
<thscope="row">{{i}}
<span*ngIf="task.queued"class="labellabel-infong-animate">
Queued
</span>
</th>
<!--therestofthetemplateremainsuntouched-->
</tr>
Saveeverythingandthenrunagaintheapplicationwhileinspectingthecodeinthebrowserdevtools.Well,apparentlynothinghappens.Butwearetalkingaboutanimationshere,andasamatteroffacttheunderlyingprocessisexpectingexactlythat,solet'sdecoratetheng-animateclasswithsomeanimation-relatedstylingdefinedinthecomponent'sassociatedstylesheet:
app/tasks/tasks.component.css
h3,p{
text-align:center;
}
.table{
margin:auto;
max-width:860px;
}
.ng-animate{
transition:all0.3sease-in;
}
TheCSSruledefinedpreviouslywillforceanystylingchangeappliedtotheDOMelementto
www.EBooksWorld.ir
takeplacein10secondsfollowinganease-inalgorithmcurvewhenapplied.Runthecodeagainandinspectthecode:apparentlywe'renowintosomething:allelementsflaggedwiththeng-animateclassnowfeaturesomeclassesthattemporarilydecoratetheelement.Theseclassesareng-enterandng-enter-active,wherethefirstoneisenabledbydefaultwhenrenderingtheelementandthelatterappliedstraightaway.After10seconds,whichisthescopeofthetransitionwedefinedintheCSSrule,bothclassnameswilldisappear,leavingtheng-animateclassastheonlytrackofitsnowextinctexistence.
Basically,wehavethesamebehaviorweimplementedbyhandintheprevioussection,withthesoleexceptionthattheclassname'sbindingisoperatedthistimebyAngular2itself.Withallthisinmind,let'srepurposethestylesheetalittlebittoleveragethebriefexistenceoftheseclassnamesinourDOMandthetransitionpropertytomakeanicefade-ineffectoccuruponloading.Replacethe10secondstransitionperiodbyashorter0.3secondvalue(300milliseconds)andlet'sdefineopacityvaluesfortheinitialng-enterclassnameandthefinalng-enter-activeclassnames.Thecodeisasfollows:
app/tasks/tasks.component.css
...
.ng-animate{
transition:all0.3sease-in;
}
.ng-enter{
opacity:0;
}
.ng-enter-active{
opacity:1;
}
Saveandrerunthecodeexamples.Now,youwillseehowthetasklistsmoothlyfadesinonscreen.Thesameappliestothebluelabelinformingifanygiventaskhasbeenqueuedupornot.
Classhooksavailable
TheclassnameswehavejustseenareknowninAngular-landasclasshooks,whichresonatesfromthedirectiveorroutinglifecycleeventswealreadycovered,andeachoneofthemwillonlyexistwhiletheanimationtakesplace.Wehavefourclasshooks:
ng-enter:ThiswillbeappliedbyAngular'sDOMrenderertoanyelementflaggedwiththeng-animateclassnameuponbeingattachedtotheview.Itusuallywrapsthestylingwerequireourcomponenttofeaturebydefault.ng-enter-active:ThisclassnameistemporarilyattachedtotheelementonruntimerightbeforestartingtheCSSanimationandisremovedautomatically,alongwiththeng-enterclassname,whentheanimationiscompleted.Itusuallydefinesthestylingwestriveourcomponenttoassumebytheendoftheanimationwhichwaspreviouslyresetbyng-enter.ng-leave:Thinkofthisclasshookasthecounterpartofng-enter,butittakesplace
www.EBooksWorld.ir
whentheelementisabouttobedetachedfromtheview.ng-leave-active:Thisissameasng-enter-active,butittakesplacewhentheelementisabouttobedetachedfromtheview.Theclassnameisappliedtotheelementandwillberemoved,alongwithng-leave,oncethetransitioniscompletedandbeforetheelementisremovedfromtheDOM.
Tip
DonoteverforgetthatthistechniqueisonlyavailableforDOMelementsthatarehandledbytheDomRenderertype,whichisalow-levelclassinchargeofcreating,updating,orremovingnodesandviewsamongothertasks.Inourcase,elementsdecoratedwiththeNgIf,NgFor,orNgSwitchdirectivesaretheonlyfeasiblecandidatesforit.
www.EBooksWorld.ir
AnimatingcomponentswiththeAnimationBuilderIfyoueverdecidetoinvestigatehowtheCSSclasshookstriggeredbytheng-animateclassbindingworkunderthehood,youwillbepositivelysurprisedbythefactthatalltheheavyliftingiscarriedoutbyaninstanceoftheCssAnimationBuilderclassinstantiatedthroughtheAnimationBuilderAPI.
TheAnimationBuilderclass(whichisaninjectabletypeandthereforesubjecttobeimportedthroughtheconstructorofourcomponents)isafactorytypewhoseAPIprovidesaccesstoinstantiatemorespecializedanimationbuilderssuchastheCssAnimationBuilderclass.ThistypehasaverybroadandpowerfulAPIwhosemethodsallowustoaddorremoveCSSclassnamesinordertotriggertransitionsoranimations,orevenconfigurebyhandanimationparameterssuchasstyles,duration,ordelayonourDOMelementsofchoice.Inthissense,wecandefinegeneralpurposeanimationhandlersandthenusethemasanimationadaptersforanyDOMelement.Inthatsense,animationhandlerscreatedbytheCssAnimationBuilderareagnosticoftheDOMelements.Therefore,asingleanimationadaptercanbeappliedtooneormanyHTMLelements.
So,inordertocreateourownanimationsprogrammaticallyusingonlyJavaScript,wejustneedaninstanceoftheAnimationBuildertype,whichwewillusetoinstantiateaCssAnimationBuilderforcreatingaspecific(HTMLnode-agnostic)animatedtransition.Lastbutnotleast,anaccessortypetotheDOMelementswewanttoanimate.Soundsdaunting?Aquickandeasyexamplewillclarifyallthis.
AgoodwaytoputallthesetothetestistointroduceanewvisualeffectonourPomodorotimer:arenderinganimationeffectwhenthecomponentisloaded,soeverytimeweactivatethePomodoroTimerroute,thecomponentgetsloadedgracefullybyfadinginonscreen.
Let'sbeginbyimportingnewtokensintheblockofimportstatementsofTimerWidgetComponent.WewillneedtofetchElementReffromangular/core,sinceitwillgiveusaccesstotheDOMelementwewanttoanimate.ThenwewillhavetobringtheAnimationBuildersymbol,whichwillbeusedtoinstantiateaCssAnimationBuilderobject.Thelatterisalsoimportedsowecanproperlyannotateourclassmembers.Thecodeisasfollows:
app/timer/timer-widget.component.ts
import{Component,OnInit,ElementRef}from'@angular/core';
import{RouteParams,CanReuse,OnReuse}from'@angular/router-deprecated';
import{SettingsService,TaskService}from'../shared/shared';
import{AnimationBuilder}from'@angular/platform-
browser/src/animate/animation_builder';
import{CssAnimationBuilder}from'@angular/platform-
browser/src/animate/css_animation_builder';
www.EBooksWorld.ir
...
Note
PleasenotethesourcelocationsforAnimationBuilderandCssAnimationBuilder.Atthetimeofwriting,the@angular/animatebarrelhasnotbeenregisteredinanyspecificbundlewithintheAngular2framework,soweneedtousethefullpathforimportingeachsymbol.ThismaychangeinthefuturesopleaserefertotheofficialAngular2documentationincaseyougeta404errorwhenimportingthetypes.
Withallthesetoolsinplace,wecanbeginsettingupthefoundationforouranimation.Firstlet'screateanewmemberinourcomponentcontrollerclassunderthenameoffadeInAnimationBuilder.ThisnewclasspropertywillrepresenttheCssAnimationBuilderinstanceobjectthatwilldefinetheanimatedtransitionweareabouttobuildnow.First,let'sinjectthedependenciesweneedthroughtheclassconstructor,properlyprefixedwithaccessmodifierssotheybecomeclassmembersinstantly.Thecodeisasfollows:
app/timer/timer-widget.component.ts
...
exportdefaultclassTimerWidgetComponentimplementsOnInit,CanReuse,OnReuse{
minutes:number;
seconds:number;
isPaused:boolean;
buttonLabelKey:string;
buttonLabelsMap:any;
taskName:string;
fadeInAnimationBuilder:CssAnimationBuilder;
constructor(
privatesettingsService:SettingsService,
privaterouteParams:RouteParams,
privatetaskService:TaskService,
privateanimationBuilder:AnimationBuilder,
privateelementRef:ElementRef){
this.buttonLabelsMap=settingsService.labelsMap.timer;
this.fadeInAnimationBuilder=animationBuilder.css();
this.fadeInAnimationBuilder.setDuration(1000)
.setDelay(300)
.setFromStyles({opacity:0})
.setToStyles({opacity:1});
}
//Restoftheclassremainsthesame
}
TheAnimationBuilder.css()methodisusedintheconstructorimplementationtoinstantiateaCssAnimationBuilderobject,anditisdirectlyassignedtothefadeInAnimationBuildermember.Onceassigned,wecanusetheCssAnimationBuilderAPItodefineananimationthatwillkickoffafter300milliseconds,afterwhichwillendurealong1000milliseconds(thatis,asecond)astyletransitiononanygivenDOMelementfromfulltransparencytosolidcolor
www.EBooksWorld.ir
state.ItisworthremarkingthattheCssAnimationBuilderfeaturesachainableAPI,sowecanconvenientlychainsettingsoneafteranother.
Butaswepointedoutinthebeginningofthissection,thisanimationhandleriscompletelyagnosticoftheelementsitcanbeappliedon.Let'sseehowwecanmakeallthistransitionhappenonanactualDOMelement.Todoso,wecantriggertheanimationusingthestart()methodexposedbytheCssAnimationBuilder.ThismethodexpectsanHTMLelementinitssignature,onwhichtheconfiguredanimationswillbeapplied.GotothengOnInit()hookandaddthefollowingblockofcodeattheend:
app/timer/timer-widget.component.ts
...
ngOnInit():void{
this.resetPomodoro();
setInterval(()=>this.tick(),1000);
lettaskIndex=parseInt(this.routeParams.get('id'));
if(!isNaN(taskIndex)){
this.taskName=this.taskService.taskStore[taskIndex].name;
}
this.fadeInAnimationBuilder.start(
this.elementRef.nativeElement.firstElementChild);
}
...
Aswementioned,thestart()methodwillexpectanHTMLelementonwhichtoapplytheanimationsetupandthatwedobyfeedingthefunctionwiththefirstchildnodeofthenativeElementpropertyoftheelementRefclassmember.TheElementReftypeweimportedintheconstructorgivesusareferencepointertothecomponentdirectiveitself(thisisthePomodoroTimercomponent)inthecontextoftheparentviewortemplateitcurrentlyexists.ItsnativeElementpropertygivesusaccesstotheunderlyingnativeelementofthecomponent,whichisusuallythetemplateHTMLnodestree.Fromthatpointonward,wejustneedtofetchitsfirstElementChildpropertyvalue,whichwillpointtotherootnodeofthecomponenttemplate,andapplytheanimationtweeningonit.Itisimportanttoremarkthatthecomponentwillnotreacttoanyanimationapplieddirectlytoit.Therefore,weneedtotraversethenativeElementpropertyaftertheactualDOMelementwewanttoanimate.WecanintroduceotherDOMselectorshere,leveragingthewebelementAPImethodssuchasdocument.getElementById()ordocument.querySelector(),butthisisdiscouragedsinceitcreatesatightcouplingbetweenthecontrollerandtherenderinglayersandcancompromisefuturemaintainabilityofourcomponents.
Now,saveallyourworkandre-runthePomodoroapplication,accessingthePomodorotimer.Voila!Ourbelovedtimernowgracefullyshowsuponscreenwithasmoothtransition.
www.EBooksWorld.ir
TheCssAnimationBuilderAPIWehavejustseenhowwecancreateagnosticanimationhandlerswiththeCssAnimationBuilder,andtodosowehaveleveragedsomepowerfulmethodsofitsAPI(suchassetDelay,setDuration,setFromStyles,orsetToStyles).ThisisjustasubsetofallmethodsavailableinitsAPI,whichencompasssomemoremethodsthatarereallyusefulforbuildingcomplexanimations.Thesemethods,includingthesignatures,areasfollows:
setDelay(delay:number):Aswesawinourexample,thissetstheanimationdelayandoverridesanyotheranimationdelaypreviouslydefinedthroughCSS.setDuration(duration:number):Thissetstheanimationdurationand,similartosetDelay(),overridesanyanimationdurationpreviouslydefinedthroughCSS.setFromStyles(from:{[key:string]:any}):Aswesawinourexample,itsetstheinitialstylesfortheanimation,intheformofahashobjectofkey/valuepairs.BecarefulwhenstylingCSSpropertiesnamedwithcamelcase.AllCSSpropertynamesmustbeconvertedtocamelcase.Inthatsense,propertiessuchasmargin-toporbackground-colorwouldturnintomarginToporbackgroundColor.setToStyles(to:{[key:string]:any}):Thissetsthedestinationstylesfortheanimation.setStyles(from:{[key:string]:any},to:{[key:string]:any}):ThisissyntacticsugartodirectlyaccessthefunctionalityprovidedbysetFromStylesandsetToStylesinasinglemethod,whichobviouslysetsstylesforboththeinitialstateandthedestinationstate.addClass(className:string):Thisaddsaclassthatwillremainontheelementaftertheanimationhasfinished.ThismethodisespeciallyusefulforoverridingCSSpropertiesonDOMelementsalreadymanagedbyCSStransitions.removeClass(className:string):Asthecounterpartofthepreviousmethod,thisremovesaclassfromtheelement.addAnimationClass(className:string):Thisaddsatemporaryclassthatwillberemovedattheendoftheanimation.Angular2leveragesthismethodunderthecoversforhandlingtheCSShookstriggeredbytheng-animateclassbindingweoverviewedatthebeginningofthischapter.start(element:HTMLElement):ThisstartstheanimationontheHTMLelementdefinedinthepayloadwhenexecutingthemethodandreturnsanAnimationobject.ThisAnimationobjectexposesveryusefulmethodswecanleveragetoimplementadditionalfunctionalitiesascallbackstobeexecutedwhentheanimationiscomplete,amongotherfunctionalities.
Allthesechainablemethodsallowustobuildreallycomplexanimationhandlersandreusethemthroughoutourapplicationswithnoeffort.
www.EBooksWorld.ir
TrackinganimationstatewiththeAnimationclassOurapplications'interactivitydoesnotendinthemomentananimationcompletesitsinterpolation.Infact,thiscanbecomethestartingpointofmanyotheranimationsorinteractiveeventsoccurringinouruserinterface.Forthatreason,itisimportantforourapplicationstobeabletodetectwhenananimationcompletesitsinterpolation.
Fortunately,wehavetheAnimationclassforthis,andtheCssAnimationBuilder.start()methodpreciselyreturnsaninstanceofthistype,aswecanseeinthefollowingexample:
app/timer/timer-widget.component.ts
...
ngOnInit():void{
this.resetPomodoro();
setInterval(()=>this.tick(),1000);
lettaskIndex=parseInt(this.routeParams.get('id'));
if(!isNaN(taskIndex)){
this.taskName=this.taskService.taskStore[taskIndex].name;
}
constanimation=this.fadeInAnimationBuilder.start(
this.elementRef.nativeElement.firstElementChild);
animation.onComplete(()=>console.log('Animationcompleted!'));
}
TheAnimationclassexposesinitsAPItheonCompleteeventhandler,whichisfiredassoonastheanimationtriggeredbytheCssAnimationBuilder.start()methodfinishes.So,wecanleverageittotriggeranyotheractioninourapp,suchasloggingoperations(asdepictedintheprecedingexample)orfurtheranimations.
RegardingtheAnimationclass,itisinfacttheonethatcarriesoutallthehardworkofmanagingthetransition.Inthatsense,theCssAnimationBuilderisjustafacadeprovidingafriendlyinterfaceforsettinguptheanimationflow.Whenitcomesthentoperformingfurtheroperationsoncetheanimationisongoingorhasjustfinished,theAnimationclassisouronlyresource.
Allinall,wewillrarelyinteractwiththeAnimationclassbeyondusingitscallbackfunctionsorleveragingitsbuilt-inmethodstohandleCSSclassesorswappingstyleswhentheanimationisover.Ontheotherhand,itisnotanInjectableclasssowecannotinstantiateitusingAngular'sdependencyinjectionsystem.Forthesereasons,wewillnotcoveritsAPIindetailhere.
www.EBooksWorld.ir
DevelopingcustomanimationdirectivesWehavesaidseveraltimesthatoneoftheadvantagesoftheCssAnimationBuilderAPIisitsreusability.PuttingtogetheranygivenanimationsetupandapplyingitonnotonebutmanyHTMLelementslateronbecomesabreeze.However,directivesaretheperfectsolutionwhenitcomestomanagingreusabilityintheAngulararena.Sowhynotgetthebestofbothworlds?Aswewillseeinthenextexample,wrappinganimationwithindirectivesbecomesthego-tosolutionformanycasescenarios.
Ourlastanimationexampleinthischapterwillintroduceabrandnewcustomdirectiveintoourapplication.TheHighlightdirectiveleveragestheCssAnimationBuilderAPItochangethebackgroundcolorofanygivenDOMelementonthefly,resettingthebackgroundcolortoitsoriginalstateattheendoftheanimation.ThiskindofflashingeffecthasbecomequitewidespreadinmodernwebdesignformakingtheuserawarethatsomethinghasjusthappenedonsomepartoftheUI.
Let'sstartbycreatingthedirectivecontrollerclassfileinsidethedirectivessubfolderofoursharedfeaturesfolder,andpopulateitwiththefollowingscript.PleasenotehowwekeepapplyingthefilenamingconventionsweembraceandhowourdirectivewillbemappedtoaCSSclassselectorthistime:
app/shared/directives/highlight.directive.ts
import{Directive,ElementRef,OnInit}from'@angular/core';
import{AnimationBuilder}from'@angular/platform-
browser/src/animate/animation_builder';
import{CssAnimationBuilder}from'@angular/platform-
browser/src/animate/css_animation_builder';
@Directive({
selector:'.pomodoro-highlight',
providers:[AnimationBuilder]
})
exportdefaultclassHighlightDirective{
cssAnimationBuilder:CssAnimationBuilder;
constructor(
privateanimationBuilder:AnimationBuilder,
privateelementRef:ElementRef){
this.cssAnimationBuilder=animationBuilder.css()
.setDuration(300)
.setToStyles({backgroundColor:'#fff5a0'});
}
ngOnInit(){
letanimation=this.cssAnimationBuilder.start(
this.elementRef.nativeElement
);
www.EBooksWorld.ir
animation.onComplete(()=>{
animation.applyStyles({backgroundColor:'inherit'});
});
}
}
Thecodeisprettysimpleinitsimplementation.WebasicallybuildadirectivemappedtoaCSSclassselectorwhoseconstructorinstantiatesaCSSanimationconsistingofabackground-colorinterpolationtoacertaintoneofyellow(definedbythe#fff5a0hexvalue)along300milliseconds.Thisisourdesiredflashyeffect.ThengOnInithookmethod,whichisexecutedintheverymomentthatthecomponentaffectedbythisdirectiveisrenderedintheview,firestheanimationandresetsthebackgroundcoloroftheaffectedDOMelementbacktoitsoriginalvalue.Aswecansee,wearetakingadvantageoftheapplyStyles()methodoftheAnimationclass.Thismethod,alongwithothermethodsexposedinitsAPIsuchasaddClasses()orremoveClasses()(bothexpectinganstringarraywiththeclassnamestoaddorremove,respectively),allowsustointeractwiththeCSSbindingsoftheanimatedDOMelement.
Beforemovingon,weneedtoensurethisnewdirectiveisavailablefortherestoffeaturescoexistinginourapplication,soweneedtoexposethisnewdirectivefromthesharedfacademoduleaswell.Thecodeisasfollows:
app/shared/shared.ts
importQueueablefrom'./interfaces/queueable';
importTaskfrom'./interfaces/task';
importFormattedTimePipefrom'./pipes/formatted-time.pipe';
importQueuedOnlyPipefrom'./pipes/queued-only.pipe';
importAuthenticationServicefrom'./services/authentication.service';
importSettingsServicefrom'./services/settings.service';
importTaskServicefrom'./services/task.service';
importRouterOutletDirectivefrom'./directives/router-outlet.directive';
importHighlightDirectivefrom'./directives/highlight.directive';
constSHARED_PIPES:any[]=[
FormattedTimePipe,
QueuedOnlyPipe
];
constSHARED_PROVIDERS:any[]=[
AuthenticationService,
SettingsService,
TaskService
];
constSHARED_DIRECTIVES:any[]=[
RouterOutletDirective,
HighlightDirective
];
www.EBooksWorld.ir
export{
Queueable,
Task,
FormattedTimePipe,
QueuedOnlyPipe,
SHARED_PIPES,
AuthenticationService,
SettingsService,
TaskService,
SHARED_PROVIDERS,
RouterOutletDirective,
HighlightDirective,
SHARED_DIRECTIVES
};
Asyoucanseeinthenewrefactoredfacade,theHighlightdirectiveispartoftheSHARED_DIRECTIVESsymbol.Therefore,itisavailableforuseonanycomponentalreadydeclaringthattokeninitsdirectivesproperty,suchasTasksComponent,whosetemplateweareabouttotweaknow.
Openthecomponent'stemplateanddecoratethengForelementwithanadditionalclass,asfollows:
app/tasks/tasks.component.html
<tr*ngFor="lettaskoftasks;leti=index"
class="ng-animatehighlight">
Reloadtheapplicationandrejoicebywatchinghowourtasklistflashesuponloadingonscreen,justtoreturnbacktoitsnormalstate.RememberthatwecanapplythesamebehaviortoanyotherpieceofDOMinourapplicationjustbyimportingthedirectiveandbindingtheclassnameintheDOMelementofourchoice.However,youareprobablywonderingwhywebuiltallthisboilerplatefordeliveringjustaflashyeffect.Wouldn'titbeeasiertowrapeverythingaroundaCSSclassperhaps?Well,thatiscorrect...unlessyouwanttointeractwiththeanimation,andthatiswhatwearegoingtodonext.
www.EBooksWorld.ir
InteractingwithourdirectivefromthetemplateInfairness,havingadirectivetriggeringananimationlikethismakesnosense,butitwouldbegreatifwecouldinteractwiththeanimation.Moreover,ifwecouldactuallyinteractrightfromthetemplate.Todoso,wecanassignanexportabletokennametoourdirectivesowecanrefertoitfromthesameelementintervenedbythedirective.DoyourememberhowweusedtorefertothengFormdirectivewhenhandlingforms?Here,wetakeadvantageofthesametechnique,aswewillseelater.First,proceedtoupdatetheHighlightdirectivebyaddinganewpropertytothedirectivesetupnamedexportAs,withthevaluehighlight.ThevaluedefinedtherewillbecomethenameweshouldrefertowhentryingtoaccessthedirectiveAPIfromwithinoutside.Howshallwedothis?Alittlebitofpatience,firstlet'sditchthengOnInitmethodbychangingitsnametocolorize,therebyremovingthetypefromthefirstlineofimportsandtheinterfaceimplementationfromtheclass.Thecodeisasfollows:
app/shared/directives/highlight.directive.ts
import{Directive,ElementRef}from'@angular/core';
import{AnimationBuilder}from'@angular/platform-
browser/src/animate/animation_builder';
import{CssAnimationBuilder}from'@angular/platform-
browser/src/animate/css_animation_builder';
@Directive({
selector:'.pomodoro-highlight',
providers:[AnimationBuilder],
exportAs:'pomodoroHighlight'
})
exportdefaultclassHighlightDirective{
cssAnimationBuilder:CssAnimationBuilder;
constructor(
privateanimationBuilder:AnimationBuilder,
privateelementRef:ElementRef){
this.cssAnimationBuilder=animationBuilder.css()
.setDuration(300)
.setToStyles({backgroundColor:'#fff5a0'});
}
colorize(){
letanimation=this.cssAnimationBuilder.start(
this.elementRef.nativeElement
);
animation.onComplete(()=>{
animation.applyStyles({backgroundColor:'inherit'});
});
}
}
Reruntheexample.Nowthetabledoesnotflashuponloading,whichisfine.Withallthisin
www.EBooksWorld.ir
place,it'stimetoupdateourtemplate.First,wewilladdalocalreferencenamedrow(orwhatevernameyoufancy)inthesameHTMLnodeimpactedbythedirective,pointingtohighlightwhichis,aswenowknow,thepublicnameofourdirective.SameasweusedtodowhenreferencingthestateandvalidityofourformswithngForm,nowwecanaccessthedirectivepublicAPI,whichexposesthecolorize()methodwejustcreatedoutoftheformerngOnInitinterfacemethod.Wecansafelyexecutethatmethodnowjustbypointingtothelocaltemplatereference,likethis:
app/tasks/tasks.component.html
<tr*ngFor="lettaskoftasks;leti=index"
class="ng-animatepomodoro-highlight"
#row="pomodoroHighlight"
(click)="row.colorize()">
Reloadtheapplicationandclickonanytask,queuingitupandoff.Itsrowwillflashmomentarily.
Tip
IntheimplementationoftheHighlightdirective,wehardcodedtheCSSvalueinbodyofthedirective.Inordertomakethisdirectivemoresustainableandscalable,weshouldpreventthisapproachinlargerapplications.Asapersonalexercise,wewouldsuggestyoutorefactorthedirectivetouseanattributeselector,suchas[pomodoroHighlight],whosevalueisparsedbyaclassmemberannotatedwiththe@Inputdecorator,soyoucanconfigurecustomflashingcolorswhenbindingthedirectiveinyourcomponenttemplates.
www.EBooksWorld.ir
LookingintothefuturewithngAnimate2.0MostoftheprocedureswehaveseenalongthischapterinherittheanimationlogicalreadypresentinAngular1.x,whichwasbasedonattachingCSSclassesandrelyingonkeyframeanimationspreviouslycreatedinourstylesheets.Unfortunately,thisapproachfallsshortwhenitcomestoperformanceoptimization,implementingconcurrentanimationsorperformingadvancedinteractions,liketheonesproposedinMaterialDesign,justtonameafewshortcomings.
ThisiswhytheAngularteamisnowworkingonanewsetofmodulesthatwilladdanunparalleledlayerofperformanceandabstractionthroughtheuseofadomainspecificlanguage(DSL)withfullprogrammaticAPI.ThisprojectwilltakeforminwhatisknownasngAnimate2.0,anditwillbereleasedatsomepointlaterin2016.TheapproachproposesaprogrammaticsystemthatreturnstoJavaScript,tappingintoCSSandallowingforprogrammaticcontrolallaround.
Inanutshell,someofthegreatimprovementsthatngAnimate2.0willfeatureareasfollows:
UseananimationfactorytocreateanimationsthroughaprogrammaticAPIwithsupportforconcurrentandsequentialanimationsStaggeringanimationshandled100percentthroughJavaScript,bringingthesamelogicofCSSKeyframesanimationtoJavaScript,compoundedbyuniversalsequencingandanimationchainingPerformancetuningwithdramaticallyfewerreflowsMultiDOM-levelanimationsandasoundcontroloverCSSBettereventintegrationandfullcontrolovertheanimationflow,withtheabilitytofast-forwardorreverseananimationinterpolationprogrammaticallySolidsupportforMaterialDesignandimprovedhandlingofuserinteractionevents,mouseclicks,andsoonAdaptivestylingFulltestabilityandsupportforanimationassertsinourtestspecs
TheseandmanymorefeatureswillbecomeavailableassoonasngAnimate2.0isreleased.Atthetimeofwriting,theAPIisnotpublicyetasngAnimate2.0isaproofofconcept,soprovidingadeepercoverageofitsmechanismisoutofthescopeofthisbook.However,keepaneyeonfutureannouncements,sincengAnimate2.0willbecomeanimportantmilestoneinthewayanimationsarehandledinJavaScript.
www.EBooksWorld.ir
SummaryInthischapter,wediscussedsomeofthedifferenttechniquescurrentlyavailableforhandlinganimationsinAngular2.First,welookedatthebasicclassname-basedanimationwithCSS3transitionswiththehelpoftheAngularbuilt-indirectives.Then,wesawhowAngularprovidesclass-basedanimationhelpersoutofthebox,sowecaneasilyimplementourowntransitionsbyfollowingsomebasicconventionsinourstylesheets.Then,wediscussedprogrammatically-managedanimationdevelopmentwiththeamazinganimationbuilderscurrentlyexistinginAngular2,withsomeexamplesofhowtotakeadvantageoftheAnimationclasscallbackstotriggeractionsalongtheanimationlifecycle.
ThelastlegofthischapterwasdevotedtogettingsomeinsightsaboutngAnimate2.0,whichissetouttotakeovertheprevioustechniquesinthenearfuture.
Ournextandfinalchapterinthebookwillsumupallwehavelearnedsofarbyintroducingoneofthemostrelevantrequirementsinmodernwebapplicationdevelopment:unittesting.Angular2embracescommonindustrystandardsandlibraries,whileintroducingsomeparticulartoolstoturnunittestingintoanenjoyabletask.
www.EBooksWorld.ir
Chapter10.UnittestinginAngular2Thehardworkofthepreviouschaptershasmaterializedintoaworkingapplicationwecanbeproudof.Buthowcanweensureapainlessmaintainabilityinthefuture?Acomprehensiveautomatedtestinglayerwillbecomeourlifelineonceourapplicationbeginstoscaleupandwehavetomitigatetheimpactofbugscausedbynewfunctionalitiescollidingwiththealreadyexistingones.
Testing(andmorespecificallyunittesting)ismeanttobecarriedoutbythedeveloperastheprojectisbeingdeveloped.However,wewillcoveralltheintricaciesoftestingAngular2modulesinbriefinthischapter,nowthattheprojectisinamaturestage.
Inthischapter,youwillseehowtoimplementtestingtoolstoperformproperunittestingofyourapplicationclassesandcomponents.
Inthischapterwewill:
Lookattheimportanceoftestingand,morespecifically,unittestingReviewthedifferentpartsofaJavaScriptunittestDiscoverJasmine,ourtestingframeworkofchoiceLearnhowtosetupaunittestingenvironmentwithJasmineandSystemJSBuildatestspectestingapipeDesignunittestsforcomponents,withorwithoutdependenciesPutourroutestothetestImplementtestsforservices,mockingdependencies,andstubsInterceptXHRrequestsandprovidemockedresponsesforrefinedcontrolDiscoverhowtotestdirectivesascomponentswithnoviewIntroduceotherconceptsandtoolssuchasKarma,codecoveragetools,orE2Etesting
www.EBooksWorld.ir
Whydoweneedtests?Whatisaunittest?Ifyou'refamiliaralreadywithunittestingandtest-drivendevelopment,youcansafelyskiptothenextsection.Ifnot,let'ssaythatunittestsarepartofanengineeringphilosophythattakesastandforefficientandagiledevelopmentprocessesbyaddinganadditionallayerofautomatedtestingonthecodebeforeitisdeveloped.Thisisthecoreconceptisthateachpieceofcodeisdeliveredwithitsowntest,andbothpiecesofcodearebuiltbythedeveloperwhoisworkingonthatcode.Firstwedesignthetestagainstthemodulewewanttodeliver,checkingtheaccuracyofitsoutputandbehavior.Sincethemoduleisstillnotimplemented,thetestwillfail.Hence,ourjobistobuildthemoduleinsuchawaythatitpassesitsowntest.
Unittestingisquitecontroversial.Whilethereisacommonagreementabouthowbeneficialtest-drivendevelopmentforensuringcodequalityandmaintenanceupontimeis,noteverybodyundertakesunittestingintheirdailypractice.Whyisthat?Well,buildingtestswhilewedevelopourcodecanfeellikeaburdensometimes,particularlywhenthetestwindsupbeingbiggerinsizethanthepieceoffunctionalityitaimstotest.
However,theargumentsfavoringtestingoutnumbertheargumentsagainstit:
Buildingtestscontributestobettercodedesign.Ourcodemustconformtothetestrequirementsandnottheotherwayaround.Inthatsense,ifwetrytotestanexistingpieceofcodeandwefindourselvesblockedatsomepoint,chancesarethatthepieceofcodeweaimtotestisnotwelldesignedandshowsoffaconvolutedinterfacethatrequiressomerethinking.Ontheotherhand,buildingtestablemodulescanhelptoearlydetectsideeffectsonothermodules.Refactoringtestedcodeisthelifelineagainstintroducingbugsinlaterstages.Anydevelopmentismeanttoevolvewithtime,andoneveryrefactortheriskofintroducingabugthatwillonlypopupinanotherpartofourapplicationishigh.Unittestsareagoodwaytoensurethatwecatchbugsinanearlystage,eitherwhenintroducingnewfeaturesorwhenupdatingexistingones.BuildingtestsisagoodwaytodocumentourcodeAPIsandfunctionalities.Andthisbecomesapricelessresourcewhensomeonenotacquaintedwiththecodebasetakesoverthedevelopmentendeavor.
Theseareonlyafewarguments,butyoucanfindcountlessresourcesonthewebaboutthebenefitsoftestingyourcode.Ifyoudonotfeelconvincedyet,giveitatry.Otherwise,let'scontinuewithourjourneyandseetheoverallformofatest.
www.EBooksWorld.ir
PartsofaunittestinAngular2Therearemanydifferentwaystotestapieceofcode,butwewillfocusonhowAngular2aimstotestmodules.Thefirstthingweneedisatestframework,providingutilityfunctionsforbuildingtestsuitescontainingoneorseveraltestspecseach.
describe('Thesubmitcomponent',()=>{//Testsuite
it('shouldberendereddisabledbydefault',()=>{//Testspec
//...Testspecimplementationgoeshere
});
});
Eachtestspecchecksoutaspecificfunctionalityofthefeaturedescribedinthesuitedescriptionargument,anddeclaresoneorseveralexpectationsinitsbody.Eachexpectationtakesavalue,whichwecalltheexpectedvalue,andiscomparedagainstanactualvaluebymeansofamatcherfunction,whichcheckswhetherexpectedandactualvaluesmatchaccordingly.Thisiswhatwecallanassertion,andthetestframeworkwillpassorfailthespecdependingontheresultofsuchassertion.Thecodeisasfollows:
describe('Thesubmitcomponent',()=>{//Testsuite
it('shouldberendereddisabledbydefault',()=>{//Testspec
//Testassertionbasedonaninstanceofthesubmitcomponent
expect(submitComponent.disabled).toBe(true);
});
});
Inthepreviousexample,submitComponentInstance.disabledwillreturntheactualvaluethatissupposedtomatchtheexpectedvaluedeclaredinthetoBe()matcherfunction.
www.EBooksWorld.ir
DependencyinjectioninunittestsItisquitecommontoperformseveraloperationsbeforeexecutingeachspec:instantiatingthecomponentorserviceclasswewanttotest,fetchingadependency,declaringamockargument,andsoon.ThemostcommonoperationwhentestingAngular2modulesistooverridethedefaultprovidersoftheinjectorwiththebeforeEachProviders()function.Thisfunctionmustbeexecutedbeforeexecutinganythingelseandtakesthisshape:
describe('Thesubmitcomponent',()=>{
beforeEachProviders(()=>[
TestComponentBuilder,
MyCustomService,
provide(Router,{useClass:RootRouter})
]);
it('shouldberendereddisabledbydefault',()=>{
expect(submitComponent.disabled).toBe(true);
});
});
Inthisexampleandpriortoexecutinganyspec,wearesettingupalistofDIprovidersweneedourtestinjectortobeawareof.Aswecansee,wecanjustdeclareclasstokensorevenoverridedependenciesbybindingatokentoaclass,value,orfactoryfunctionofourchoicewiththeprovide()function.Infact,replacingmoduledependenciesbymocktypeswithfakedataorfunctionalityisindeedaquitecommonpracticewhentesting.
We'restillnotdoneinregardsofDI:whataboutinstantiatingtheobjectswerequirefortestingorresettingthefixtureinformationweneedforeachtest?ThebeforeEach()functionisthenaturalplaceforfetchingthedependenciespreviouslydeclaredinbeforeEachProviders(),instantiatingtheclassesweneedtotest,orperforminganyoperationrequiredforpreparingtheobjectsordatafixturesourtestspecsneed.ItmustbeexecutedafterthebeforeEachProviders()andbeforethetestspecs.Inordertodoso,itleveragestheinject()function.
Inthefollowingpieceofcode,westripdownupabittheexampleprovidedandintroduceallthenecessarysteps.Pleasepayattentiontotheinlinecodecomments,sincetheydescribetheintentofeachblockofcode:
describe('Thesubmitcomponent',()=>{
//Wedeclarethedependenciesweneedtheprovidertomanage
beforeEachProviders(()=>[
TestComponentBuilder
]);
//Avariablewillallocatethetestcomponentbuilder
lettestComponentBuilder:TestComponentBuilder;
www.EBooksWorld.ir
//Beforeeachspecwefetchaninstanceof
//TestComponentBuilderrightfromtheinjector
beforeEach(inject([TestComponentBuilder],
(tcb:TestComponentBuilder)=>{
testComponentBuilder=tcb;
})
);
//Thedonefunctionargumentconfiguresourspecasasynchronous
it('shouldberendereddisabledbydefault',done=>{
//Thetestcomponentbuilderasynchronouslyresolvestoa
//fixtureobjectwrappinganactualinstanceofSubmitComponent
testComponentBuilder
.createAsync(SubmitComponent)
.then(testComponent=>{
//Wefetchthecomponentinstance
//outfromthefixturewrapper
constsubmitComponent=testComponent.componentInstance;
//Weevaluatetheassertionwithagivenmatcherfunction
expect(submitComponent.disabled).toBe(true);
//finally,thedone()functionresolves
//theasynchronousspec
done();
});
});
});
DonotworryabouttheTestComponentBuilder,wewillcoveritshortly.PayspecialattentiontothewaywegetaninstanceoftheTestComponentBuildertypebypassingtheclasstokentotheinject()function,whichwillreturninitscallbackargumentthedesiredinstancethatwewillbindtoavariableintheouterscopeforlateruseinourspecs.AmoreexpressivewaytofetchobjectinstancesthroughtheinjectorwouldbetofetchanactualInjectorobjectinstanceandthenleverageitsget()helpermethod:
letmyCustomService;//Acustomserviceofours
beforeEach(inject([Injector],(injector:Injector)=>{
myCustomService=injector.get(MyCustomService);
})
);
Ultimately,choosethesyntaxyoufindmoreconvenient.Therestoftheexamplebasicallyentailsthecommonoperationsrequiredwhentestingcomponents:creatingatestingcomponentfixture,fetchinganactualcomponentinstancefromthefixturewrapper,conductingassertionsonthecomponentinstance,andfinallyresolvingtheasynchronousspecwiththedone()command.Wewillseealltheseindetailonceweoverviewcomponenttestinglateroninthischapter.
Therearemoreelementsinaunittest(mocks,spies,andsoon),butwewillseethemost
www.EBooksWorld.ir
commonsuspectsalongthefollowingpages.
Therearetwoimportantquestions:howdoallthesetestsareexecutedandwheredowecheckthepass/failresults?Forthefirstquestion,let'sjustsaywewillneedatestingframeworkforJavaScript,andatthetimeofthiswriting,Jasmine(http://jasmine.github.io)seemstobethepreferredtestingframeworkintheAngularteam.Regardingthesecondquestion,wewillneedaspecrunner,andJasminepreciselyprovidesanHTML-basedrunnerout-of-the-box,solet'sseehowwecanconfigureJasminetodothis.
www.EBooksWorld.ir
SettingupourtestenvironmentOurfirststepwillbetodownloadandintegrateJasmineinourproject.Todoso,gototheconsoleinourprojectandexecutethefollowingnpmcommandthatwillinstallthejasmine-corepackage(includingtheexactversionsofthedependenciesrequiredratherthanusingnpm'sdefaultsemverrangeoperator,hencethe--save-exactflag)requiredforourtests:
$npminstalljasmine-core--save-dev--save-exact
Withthepackageinstalled,weneedtoensurethatourprojectcontainsthepropertypingsfortheJasmineglobalobjectsandfunctionmatchers,sothecompilercanrecognizethetypesandthusbuildourtests:
$typingsinstalljasmine--save--ambient
Withallthelibrariesinplace,let'sseehowwewillputtogetheraspecrunnertoprocessourtestsandoutputresults.
www.EBooksWorld.ir
ImplementingourtestrunnerWearegoingtorunourtestsinabrowser,andinordertodosowewillneedtosetsomebasetestingprovidersthatarespecifictothebrowserplatform.Createafoldernamedtestattherootofourprojectandsavethefollowingfilethere:
test/setup.ts
import{resetBaseTestProviders,setBaseTestProviders}from
'@angular/core/testing';
import{BROWSER_APP_DYNAMIC_PROVIDERS}from"@angular/platform-browser-
dynamic";
import{
TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
ADDITIONAL_TEST_BROWSER_PROVIDERS
}from'@angular/platform-browser/testing';
resetBaseTestProviders();
setBaseTestProviders(
TEST_BROWSER_STATIC_PLATFORM_PROVIDERS,
[
BROWSER_APP_DYNAMIC_PROVIDERS,
ADDITIONAL_TEST_BROWSER_PROVIDERS
]
);
Wewillimportthisfileintoourtestingimplementationshortlyandthereisnoneedtocoveritindetailatthispoint.Let'sjustsaytheseproviderscontainDOMadaptersrequiredtoperformcertainoperationsinShadowDOMimplementations.
Beforemovingon,itisimportanttohighlightoneoftheconventionsusedinthisbookforAngular2unittests:filenamingandspeclocation.Thereisageneralagreementonsavingourtestspecfilesinthesamelocationwherethetestedmodulelives.Inordertomakethingsevenclearer,wewillnamethespecfilesafterthenameofthecodeunittheytest,andwillappendthe.specsuffixtothefilename.Thisway,itbecomeseasiertolocatethetestscorrespondingtoeachmodule,checkwhatistestedandwhatmoduleslackatest,andgetbetteracquaintedwiththecodebase.Keepinmindthatagoodtestbecomesavaluablepieceofdocumentationbyitself.
Let'sseethisnamingconventioninactionbycreatingatemporarytestspecattherootofyourproject,sowecancheckwhetherourrunnerisworkingfine:
test.spec.ts
describe('Ourtestrunner',()=>{
it('isalive!',()=>{
expect(true).toBe(true);
});
});
www.EBooksWorld.ir
Let'screateourspecrunnerfilenowattherootofourproject.ThespecrunnerisprettysimilartothemainHTMLpageinregardstothescriptresourcesrequiredbutalsoaddsontopofthatalltherequiredincludescriptsfromJasmineandthetestingbundlefromAngular2.Thefollowingimplementationalsodeclaresanarraywithstringpointerstothelocationsofthebrowsertestingsetupfileandtheproofofconcepttestwejustbuilt,besidesfeaturingavariablestoringthepathtothecompiledTypeScriptfiles:
spec-runner.html
<!DOCTYPEhtml>
<html>
<head>
<metahttp-equiv="content-type"
content="text/html;charset=utf-8">
<title>PomodoroAppUnitTests</title>
<linkrel="stylesheet"
href="node_modules/jasmine-core/lib/jasmine-core/jasmine.css">
<scriptsrc="node_modules/jasmine-core/lib/jasmine-core/jasmine.js"></script>
<scriptsrc="node_modules/jasmine-core/lib/jasmine-core/jasmine-html.js">
</script>
<scriptsrc="node_modules/jasmine-core/lib/jasmine-core/boot.js">
</script>
<scriptsrc="node_modules/es6-shim/es6-shim.min.js"></script>
<scriptsrc="node_modules/zone.js/dist/zone.js"></script>
<scriptsrc="node_modules/reflect-metadata/Reflect.js"></script>
<scriptsrc="node_modules/systemjs/dist/system.js"></script>
<scriptsrc="node_modules/rxjs/bundles/Rx.js"></script>
<scriptsrc="systemjs.config.js"></script>
</head>
<body>
<script>
//Yourtypescriptcompiler'outDir'parametervalue
varoutDir='built';
//Enlistyourspecsinthefollowingspeccollectionarray,
//nexttothesetupimportfile,allwithnofileextension
varspecCollection=[
'test/setup',
'test.spec'//Thetestspecwejustbuilt
];
//Weloadallspecsasynchronouslyfromthebuiltfolder
//andevaluatetheiroutputatonce
Promise.all(specCollection.map(specPath=>{
returnSystem.import(`${outDir}/${specPath}`);
}))
.then(window.onload)
.catch(console.error.bind(console));
</script>
</body>
www.EBooksWorld.ir
</html>
Checktheimportscriptsandfocusonthelastblockofcode.Aswesaid,wedefinethepathtothecompiledfiles(accordingtoourtsconfig.jsonsetup)andanarrayofstringpathstothemainbrowsertestingsetupandourspecs,whichisonlyoneatthismoment.TheSystem.configimplementationisprettystraightforwardandremindsoftheonewealreadyhaveforlaunchingourprojectatindex.html.Ourlastblockisabitmorecomplexandrequiressomemoreattention.Basically,wecreateanarrayofSystem.importcommandsbymappingthesetupandspecpathsarrayintoanotherarraythatcombinesthosepathswiththeoutDirfolderlocation.EachSystem.import()executionreturnsapromise,sotheresultingarraycanbeexecutedaltogetherthroughaPromise.all()command,triggeringawindowonloadevenuponresolving.ItispreciselythiseventthatJasmineiswaitingfortorenderthepass/failreport.
Timetoseeallthisinaction!Savethechanges,runthecompilertohavetranspiledversionsofthethetestsetupfileandourjustcreatedspec,andthenrunthelocalwebserverbybrowsingtothespec-runnerfileURLinthebrowserlocationbar.Youshouldseeawebreportlikethis:
www.EBooksWorld.ir
SettingupNPMcommandsTestingourmodulesisaniterativeprocess,sowecaneasethingsabitinordertomakethewholeflowsmoother.Todoso,wecansetupsomewrappingcommandsaroundthecommontasksoferasingthecontentsofthebuildfolder,recompilingtheprojectandtriggeringawebserverpointingtoourtestrunner.Asamatteroffact,thepackage.jsonfilecanallocateatestcommand,whichwillalsotriggerpretestandposttestscriptswhenexecuted.Withthisknowledgeinhand,let'supdatethescriptsblockinourpackage.jsonfiletoincludecommandsthatperformalltheaforementionedoperations:
package.json
...
"scripts":{
"postinstall":"npmruntypingsinstall",
"tsc":"tsc",
"tsc:w":"tsc-w",
"lite":"lite-server",
"prestart":"tsc",
"start":"concurrently\"npmruntsc:w\"\"npmrunlite\"",
"typings":"typings",
"pretest":"rm-rf./built&&npmruntsc",
"test":"npmrunlite--open=/spec-runner.html"
},
...
Wheneveryouwanttorunthewholebatchoftestsonourapplication,justgototheconsoleandrunnpmtestattheprojectlocationprompt.Rememberthatthecompilerisnotexecutedinwatchmode,soduringyourdevelopmentsessionsitissafetorunnpmstartinsteadandloadthespecrunnerinaseparatebrowserwindowtochecktheevolutionofourtests,ifrequired.
We'redonewithoursetup!Inthefollowingpages,wewillgothroughdifferentexamplesoftestsstructuresforcomponents,directives,pipes,servicesandroutes,takingsomemodulesofthepomodoroappprojectasanexample.Thereisnoone-size-fits-allpatternwhenunittestingAngular2modules,butdifferentapproachesdependingonthesubjectoftest.Allinall,wewillgofindingrecurringpatternsinourtestsandtherestoftheimplementationwillexplainbyitself,sowewillnotgetintomuchdetaildescribingwhatthecodedoesineachexample.Ultimately,thatiswhatTDDisallabout:explaininghowcodeworksbyputtingittothetest.
www.EBooksWorld.ir
Angular2custommatcherfunctionsWhatkindofcheckscanweperformwithmatcherfunctionsinAngular2?Well,theansweris:quitealot.BesidesthemostcommonmatcherssuchastotoBe()ortoEqual(),wecanuseanybuilt-inJasminematcher.PleaserefertotheJasmineofficialsiteforacompleterundownonthematchersavailable.Ontopofthat,Angular2implementsasetofcustommatcherstoperformcommonoperationswhentestingAngular2specificmodules:
toBePromise():ThisexpectsthevaluetobeaPromisetoBeAnInstanceOf(expected:any):ThisexpectstheactualvaluetobeaninstanceofaclassdefinedintheexpectedargumenttoHaveText(expected:any):ThisexpectstheelementtohaveexactlythetextdefinedintheexpectedargumenttoHaveCssClass(expected:any):ThisexpectstheelementtohavetheCSSclassdefinedintheexpectedargumenttoHaveCssStyle(expected:any):ThisexpectstheelementtobestyledwiththegivenCSSstylesdefinedinthepayloadtoImplement(expected:any):ThisexpectsaclasstoimplementtheinterfaceofthegivenclasstoContainError(expected:any):ThisexpectsanexceptiontocontainanexpectederrortexttoThrowErrorWith(expectedMessage:any):ThisexpectsafunctiontothrowanerrorwiththegivenerrortextwhenexecutedtoMatchPattern(expectedMessage:any):Thisexpectsastringtomatchthegivenregularexpression
AlltheprecedingmatcherfunctionsresolvetoaBooleanvalue.Sometimes,wewillwanttoevaluateinourassertiontheoppositeofthecomparisonactuallyimplementedinthematcherfunction.Forthosecaseswejustneedtoprependa.notmodifierbeforethematcher:
expect(true).not.toBe(false);
www.EBooksWorld.ir
TestingpipesApipeisbasicallyaclassthatimplementsthePipeTransforminterface,henceexposingatransform()methodthatisusuallysynchronous.Inthatsense,pipesaretheperfectcandidatesfortakingourfirststepsintheworldofunittestingAngular2modules.WewillbeginthenbytestingFormattedTimePipe,creatingaswementionedatestspecrightnexttoitscodeunitfile.Thecodeisasfollows:
app/shared/pipes/formatted-time.pipe.spec.ts
importFormattedTimePipefrom'./formatted-time.pipe';
import{
describe,
expect,
it,
beforeEach}from'@angular/core/testing';
describe('shared:FormattedTimePipe',()=>{
letformattedTimePipe:FormattedTimePipe;
beforeEach(()=>formattedTimePipe=newFormattedTimePipe());
//Specswithassertions
it('shouldexposeatransform()method',()=>{
expect(typeofformattedTimePipe.transform).toEqual('function');
});
it('shouldtransform50into"0h:50m"',()=>{
expect(formattedTimePipe.transform(50)).toEqual('0h:50m');
});
it('shouldtransform75into"1h:15m"',()=>{
expect(formattedTimePipe.transform(75)).toEqual('1h:15m');
});
it('shouldtransform100into"1h:40m"',()=>{
expect(formattedTimePipe.transform(100)).toEqual('1h:40m');
});
});
Weimportthepipetokeninordertoinstantiateitandbindittoavariablebeforerunningeachtest,whichwillgrabthisvariableandintrospectitstypeorwillpassdifferentvaluestoitstransformmethodtocheckwhetherweobtaintheexpectedvalueornot.
Whataboutthetestingcyclewementionedearlier?Inanormalscenario,wewouldaddourtestfirst,leveragingthetestitselftodesignourpipeinterface.Ourtestwouldfailatfirstandthenwewoulddevelopitsimplementation.Whendefiningourassertions,weshouldruntheextramileanddefineassertionswhereourpipehastoconfrontwronginputsorunexpectedscenarios.Asourcodeevolvesandisrefactored,ourtestswillhavetoberefactoredand
www.EBooksWorld.ir
simplifiedaswell.
Note
ItisworthremarkingthatweareimportingtheJasmineglobalfunctionsfromtheAngular2testingbundle,andnotfromJasmineitself.ThisisbecauseAngular2overridesJasmine'sbuilt-infunctions,althoughthefunctionalityandinterfaceremainsthesame.
SavethefileanddeclareitinourspecCollectionarrayatourspecrunner(youcansafelyremovethereferencetothetestspecsincewewillnolongeruseit):
spec-runner.html
...
varspecCollection=[
'test/setup',
'app/shared/pipes/formatted-time.pipe.spec'
];
...
Inthenextsections,wewillseehowtocreatedifferenttests.
Note
Everytimewecreateanewtestspec,youwillhavetoappenditspath(withoutthefileextension)tothespecCollectionarrayvariableatspec-runner.html.Donotforgetthis,sincewewillnotmakeexplicitreferencetothisrequirementfromnowon.Failingtoincludethereferencewillturnintonon-executionofthetest.Therefore,thespecwillnotshowupinthespecrunnerreport.
www.EBooksWorld.ir
TestingcomponentsTestingpipesisprettystraightforwardbuttestingcomponentscanbecomeamoredauntingexperiencewhenapproachedforthefirsttime.Therearetoomanyquestions:howcanwetestacomponentthatneedstobebootstrappedsomewhere?GoodnewsisthatAngular2,andmorespecificallyitstestingbundle,containsaclassnamedTestComponentBuilderthatcanbeusedtoinstantiatefullyfunctionalcomponentsofanygiventype,wrappedbyafixtureobjectthatgivesusaccesstothecomponentinstanceobjectoritscompiledHTMLview.Insummary,anyinstanceoftheTestComponentBuilderexposesthefollowingpropertiesandmethods:
debugElement:ThisistheDebugElementassociatedwiththerootelementofthiscomponent.ComponentInstance:Thisreturnstheinstanceobjectoftherootcomponentclass,withfullaccesstoallitspropertiesandmethods.NativeElement:Thisreturnsthenativeelementattherootofthecomponent.DetectChanges():Thistriggersachangedetectioncycleforthecomponent.Wewanttorunthismethodinordertocheckthatthechangesoccurredonthecomponentstateshouldweupdateanyofitspropertiesorexecuteitsmethods.destroy():Thistriggerscomponentdestruction.
Withallthesepointsinmind,let'screateourfirstcomponenttest.TaskIconsComponentisaperfectcandidatetostartwith:
app/tasks/task-icons.component.spec.ts
importTaskIconsComponentfrom'./task-icons.component';
import{
describe,
expect,
it,
inject,
beforeEach,
beforeEachProviders}from'@angular/core/testing';
import{TestComponentBuilder}from'@angular/compiler/testing';
describe('tasks:TaskIconsComponent',()=>{
lettestComponentBuilder:TestComponentBuilder;
//Firstwesetuptheinjectorwithprovidersforourcomponent
//andforafixturecomponentbuilder
beforeEachProviders(()=>[TestComponentBuilder]);
//Wereinstantiatethefixturecomponentbuilder
//beforeeachtest
beforeEach(
inject([TestComponentBuilder],
(_testComponentBuilder:TestComponentBuilder)=>{
testComponentBuilder=_testComponentBuilder;
}
www.EBooksWorld.ir
));
//Specswithassertions
it('renders1imageforeachpomodorosessionrequired',done=>{
//Wecreateatestcomponentfixtureonruntime
//outfromtheTaskIconsComponentsymbol
testComponentBuilder
.createAsync(TaskIconsComponent)
.then(componentFixture=>{
//WefetchinstancesofthecomponentandtherenderedDOM
lettaskIconsComponent=componentFixture.componentInstance;
letnativeElement=componentFixture.nativeElement;
//Wesetatestvaluetothe@Inputproperty
//andtriggerchangedetection
taskIconsComponent.task={pomodorosRequired:3};
componentFixture.detectChanges();
//theseassertionsevaluatethecomponent'ssurfaceDOM
expect(nativeElement.querySelectorAll('img').length).toBe(3);
//Wefinallydestroythecomponentfixtureand
//resolvetheasynctest
componentFixture.destroy();
done();
})
.catch(e=>done.fail(e));
});
});
Takeaminutetolookattheimportstatementblockatthetopofthefile.Besidesthebasictestingfunctions,weareimportingthesymbolspertainingtotheDI-relatedfunctionsofthetestingbundle,apartfromthesetupmethodsbeforeEachandbeforeEachProviders.First,wewillexecutebeforeEachProviders,passingasanargumentalambdafunctionreturningthearrayofproviderswewillneed.Then,thebeforeEachfunctionusestheinjectortofetchanobjectinstanceoftypeTestComponentBuilder,whichwepreviouslydeclaredasaprovider,andbindsittothetestComponentBuildervariable.Insidethetestspec,weuseobjectvariabletoexecutetheasynchronouspromisifiedcreateAsync()method,whichwillreturnafixturearoundourcomponentofchoice(definedasanargument).Aswesawalreadyatthebeginningofthissection,wecaninspectthefixturetograbanactualinstanceofTaskIconsComponentanditsunderlyingnativeelement.
Then,webegininteractingwiththecomponentbyconfiguringitsproperties.Everytimeweupdateanyinputpropertyorexecuteamethodthatmightchangethecomponentstate,weneedtoexecutethefixture'sdetectChanges()methodtotriggerchangedetectionandhencereflectthatstatechangeinthenativeElement.ThisallowsustotestassertionsusingamatcherfunctiontocomparetheamountofDOMnodesgeneratedagainsttheexpectedamountofnodesconfiguredinthematcher.Finally,wedestroythecomponentandresolvethe
www.EBooksWorld.ir
asynchronousfunctionspecwe'rein.
Usually,atesthasmorethanonespec,onepereachfunctionalitydescribed,forinstance.Thus,let'saddanotheritstatementrightaftertheonewealreadyhaveinsidethedescribe()suite:
app/tasks/task-icons.component.spec.ts(continued)
...
it('shouldrendereachimagewiththeproperwidth',done=>{
testComponentBuilder.createAsync(TaskIconsComponent)
.then(componentFixture=>{
lettaskIconsComponent=componentFixture.componentInstance;
letnativeElement=componentFixture.nativeElement;
letactualWidth;
taskIconsComponent.task={pomodorosRequired:2};
taskIconsComponent.size=60;
componentFixture.detectChanges();
actualWidth=nativeElement
.querySelector('img')
.getAttribute('width');
expect(actualWidth).toBe('60');
done();
})
.catch(e=>done.fail(e));
});
...
Wecanaddasmanyspecsaswefeelnecessarytoprovideabroadcoverageofallscenarios.
Tip
Debuggingourowntests
Aswecreatemoreandmorespecs,chancesarewewillintroducesomebugsinourowntestimplementations,turningthisintoageneralfailureinourtestreport.Trackingdowntheseissuescanbeabittricky,sothebestwaytoaddressthisscenarioisbyisolatingtestsuitesorspecsexecutionor,allthewayaround,disablingtheexecutionofbrokenteststemporarily.Todoso,wecanusevariationsofthedescribe()andit()functions,byprependingalettertothefunctionname,asfollows:
fdescribe():Thisinstructsthetestrunnertoonlyrunthetestcasesinthisgroup.Itcanbeusedasddescribe()aswell.fit():Thetestrunnerwillonlyexecutethistest,disregardingalltheothers.Itcanbeusedasiit()aswell.xdescribe():Thisinstructstherunnertoexcludethistestsuitefromexecution.xit():Thisinstructstherunnertoexcludethistestspecfromexecution.
www.EBooksWorld.ir
TestingcomponentswithdependenciesIntheprevioussection,weundertookourveryfirstunittest,takingasimplecomponentwithnodependenciesasanexample.However,componentsandotherAngular2modulesusuallyhavedependenciesinjected.Theunittestneedstoreflectthiscircumstance.Let'slookatTimerWidgetComponentasanexample.Thistinycomponentrequiresallthesedependenciesinjectedthroughitsconstructor:
app/timer/timer-widget.component.ts
...
constructor(
privatesettingsService:SettingsService,
privaterouteParams:RouteParams,
privatetaskService:TaskService,
privateanimationBuilder:AnimationBuilder,
privateelementRef:ElementRef){...
Thus,inordertoinstantiatethecomponentwithinthefixturereturnedbytheTestComponentBuilderfactory,weneedtodeclaretheprovidersforthesedependenciesatthetestinjectorandhavetheminjectedsomehow.Ontopofthat,thecomponenthadsomeimportantnuancesinitsexecution:itissensitivetoURLparamsandoneofitsdependencies(TaskService)performsunderlyingXHRoperationsbymeansoftheHttpmodule.Itneedstobeinterceptedandproperlymocked.
Thismightsoundquitedaunting,butthetruthisthatyoualreadyknowallthecodeproceduresrequiredtoputtogetherthistest.Let'sseeitwithinlinecommentsinthecode:
app/timer/timer-widget.component.test.ts
importTimerWidgetComponentfrom'./timer-widget.component';
import{provide}from'@angular/core';
import{RouteParams}from'@angular/router-deprecated';
import{SettingsService,TaskService}from'../shared/shared';
import{
describe,
expect,
it,
inject,
beforeEach,
beforeEachProviders,
setBaseTestProviders}from'@angular/core/testing';
import{TestComponentBuilder}from'@angular/compiler/testing';
import{Http,BaseRequestOptions}from'@angular/http';
import{MockBackend}from'@angular/http/testing';
import'rxjs/add/operator/map';
describe('timer:TimerWidgetComponent',()=>{
lettestComponentBuilder:TestComponentBuilder;
letcomponentFixture:any;
www.EBooksWorld.ir
//Firstwesetuptheinjectorwithprovidersforourcomponent
//dependenciesandforafixturecomponentbuilder
//Note:Animationprovidersarenotnecessary
beforeEachProviders(()=>[
TestComponentBuilder,
SettingsService,
TaskService,
//RouteParamsisinstantiatedwithcustomvaluesuponinjecting
provide(RouteParams,{useValue:newRouteParams({id:null})}),
//WereplacetheHttpproviderinjectedlaterinTaskService
MockBackend,
BaseRequestOptions,
provide(Http,{useFactory:
(backend:MockBackend,options:BaseRequestOptions)=>{
returnnewHttp(backend,options);
},
deps:[MockBackend,BaseRequestOptions]
}),
TimerWidgetComponent
]);
//Wereinstantiatethefixturecomponentbuilderbeforeeachtest
beforeEach(inject([TestComponentBuilder],
(_testComponentBuilder:TestComponentBuilder)=>{
testComponentBuilder=_testComponentBuilder;
}
));
});
Youhaveprobablynoticedthereisnotasingletestassertioninthesuite.Wewillgetthereinaminute,butnowlet'sovervieweachpieceofcodeinthescript.Thetestsuiteimplementationcontainseverythingyoualreadyknowaboutinjectingprovidersintests.FirstweusebeforeEachProviders()todeclarealltheprovidersweneedtheinjectortobeawareof.Asyouwillremember,theTimerWidgetComponenthadadependencyonRouteParams,whichallowedustofetchthevalueoftheidquerystringparameter,ifany.Obviously,wenotonlyneedtoinjectthatprovider,butourinjectoroughttoreturnaninstanceofitwiththeparameterproperlypopulatedforourtestingpurposes:
provide(RouteParams,{useValue:newRouteParams({id:null})}),
AbitmoreattentionisrequiredtounderstandhowweaccomplishtheHTTPrequestsperformedbyTaskService.ThedefaultconstructoroftheHttpmodulerequiresabackendconnectionobjectimplementingtheConnectionBackendinterface.Forourtest,weneedtoprovidetheinjectorwithaworkingversionofHttpandthusweleverageMockBackend,whichimplementstheinterfacerequired.Laterinthischapter,wewillseehowwecanleveragethisclasstointerceptXHRrequestsandreturncannedresponsesforourtests.
BaseRequestOptions,
provide(Http,{useFactory:
(backend:MockBackend,options:BaseRequestOptions)=>{
returnnewHttp(backend,options);
www.EBooksWorld.ir
},
deps:[MockBackend,BaseRequestOptions]
}),
Withalltheprovidersproperlyavailablefromtheinjector,wecanleverageittoinstantiateanewTestComponentBuilderfactoryobjectbeforeexecutinganytest.
beforeEach(inject([TestComponentBuilder],
(_testComponentBuilder:TestComponentBuilder)=>{
testComponentBuilder=_testComponentBuilder;
}
));
Withallthetestingscaffoldedandready,let'sintroduceourfirsttestspec.AppendthefollowingpieceofcoderightafterthebeforeEach(...)blockwithinthebodyofthedescribe(...)suite:
it('shouldinitialisewiththepomodorocounterat24:59',done=>{
//Wecreateatestcomponentfixtureon
//runtimeoutfromthecomponentsymbol
testComponentBuilder
.createAsync(TimerWidgetComponent)
.then(componentFixture=>{
//WefetchinstancesofthecomponentandtherenderedDOM
lettimerWidgetComponent=componentFixture.componentInstance;
letnativeElement=componentFixture.nativeElement;
//WeexecutetheOnInithookandtriggerchangedetection
timerWidgetComponent.ngOnInit();
componentFixture.detectChanges();
//Theseassertionsevaluatethecomponentproperties
expect(timerWidgetComponent.isPaused).toBeTruthy();
expect(timerWidgetComponent.minutes).toEqual(24);
expect(timerWidgetComponent.seconds).toEqual(59);
componentFixture.destroy();
done();//Resolveasynctext
})
.catch(e=>done.fail(e));
});
Asyoucansee,ournewlycreatedspecexecutesseamlesslybyinstantiatingacomponentfixturewrappingthecomponentinstanceandnativeelementwerequire.Weexecutethecomponent'sngOnInit()hookmethodtoforceitsinitializationasifhadbeenrenderedonaview.Then,wetriggerthefixture'sdetectChanges()methodthatwilltriggerachangedetectioncycleonourcomponentinstance,applyinganystatechangeasaresultoftheoperationstakenplacewithinngOnInit().
Thefollowingspec,whichyoucanappendtothedescribe()bodyrightaftertheprevioustestspec,reinforcestheseconcepts:
www.EBooksWorld.ir
it('shouldinitialisedisplayingthedefaultlabels',done=>{
testComponentBuilder
.createAsync(TimerWidgetComponent)
.then(componentFixture=>{
componentFixture.componentInstance.ngOnInit();
componentFixture.detectChanges();
expect(componentFixture.componentInstance.buttonLabelKey)
.toEqual('start');
expect(componentFixture.nativeElement
.querySelector('button')
.innerHTML.trim())
.toEqual('StartTimer');
componentFixture.destroy();
done();
})
.catch(e=>done.fail(e));
});
www.EBooksWorld.ir
OverridingcomponentdependenciesforrefinedtestingInthepreviousexamples,wesawhowwecoulddeclareandinjecttheprovidersthatoursubjectsoftestingrequired.Wealsosawhowwecouldleveragetheprovide()functiontopasstheinjectoraninstanceofanygivenprovideralreadypopulatedwiththevalueswerequire.Inthatsense,provide()isnotjustusedtoreplacedependencytypesuponinjectingproviders,buttocustomizethewaywewantaparticularproviderofthatspecifictypetobeinjected.
However,canweoverrideprovidersatatestspeclevel?Theanswerisyes,anditisquiteusefulwhenitcomestomockdependencyvaluesforcertaintests.Inournexttestspec,wewillcontinuetestingthetimerwidgetcomponent.However,wewilloverridetheTaskServiceproviderthistime,replacingitbyanobjectliteralwithmockdata.WewillalsooverridethedefaultRouteParamsinjectionwithanotherinstanceobjectofRouteParams,featuringanactualvaluefortheidparameter.
Addthistestspecrightaftertheprevioustwospecswithinthebodyofthedescribe(...)function:
it('shouldinitialisedisplayingaspecifictask',done=>{
//WemocktheTaskServiceproviderwithsomefakedata
letmockTaskService={
taskStore:[{
name:'TaskA'
},{
name:'TaskB'
},{
name:'TaskC'
}
]
};
testComponentBuilder
.overrideProviders(TimerWidgetComponent,[
provide(RouteParams,{useValue:newRouteParams({id:'1'})}),
provide(TaskService,{useValue:mockTaskService})
])
.createAsync(TimerWidgetComponent)
.then(componentFixture=>{
componentFixture.componentInstance.ngOnInit();
componentFixture.detectChanges();
expect(componentFixture.componentInstance.taskName)
.toEqual('TaskB');
expect(componentFixture.nativeElement.querySelector('small'))
.toHaveText('TaskB');
componentFixture.destroy();
done();
})
www.EBooksWorld.ir
.catch(e=>done.fail(e));
});
Thecodeisprettyself-explanatory,butlet'stakesomeminutestoanalyzethisblock:
testComponentBuilder.overrideProviders(TimerWidgetComponent,[
provide(RouteParams,{useValue:newRouteParams({id:'1'})}),
provide(TaskService,{useValue:mockTaskService})
])
Basically,weleveragetheoverrideProviders()methodoftheTestComponentBuilderfactory,whichwillexpectinitsfirstargumentthetypeofthecomponentwhoseproviderswewanttooverrideandanarrayofprovidersasasecondargument.Wecaninsertinsuchanarrayanykindoftypeoverrideorreplacementbymeansoftheprovide()function.
InordertogettheoverrideProviders()towork,itparsesthecurrentproviderspropertyofthecomponentdecoratorwhoseproviderswewanttooverride.Ifthecomponentdoesnotfeaturethepropertyinitsdecorator(mostlybecauseallitsdependenciesareinheritedfromtherootinjector),Angularwillthrowanexception.So,forourexample,pleaseincludeanemptyproviderspropertyintheTimerWidgetComponentdecoratorconfiguration:
app/timer/timer-widget.component.ts
@Component({
selector:'pomodoro-timer-widget',
styleUrls:['app/timer/timer-widget.component.css'],
providers:[],
template:`…`
})
ThisissuemightbeaddressedbytheAngular2teaminthefuture,butinthemeantimeweneedtoproceedthisway.
Tip
Needtooverrideatestcomponent'sdirectivesortemplate?
YoucanoverrideotherelementsofthetestcomponentinstancewiththemethodsoverrideTemplate(),overrideView()(whichgivesyouaccesstooverridetheliteraldefiningthingssuchasstyles),oroverrideDirectives().Theirsignaturefollowsprettymuchthesameconvention,wherewedefinefirstthecomponenttypeandthen,asasecondargument,thereplacementweneedforthecomponentoriginalvalue.
PleaserefertotheofficialAPIdocumentationforfurtherdetailsifrequired.
www.EBooksWorld.ir
TestingroutesJustlikecomponents,routesplayanimportantroleinthewayourapplicationsdeliveranefficientuserexperience.Assuch,testingroutesbecomesparamounttoensureaflawlessperformance.TryingtodeclaretheRoutertokenasaproviderwouldturnintoanexception,sowebasicallyneedtosomehowinformourtestinjectoraboutwhatshouldituseasrootrouterandrootcomponent,bringinginallthedependenciesrequiredbythesetwotypes,asidefrommockingtheLocationservicewithamorespecializedservicewhichistheSpyLocationservice.Thefollowingexampleclarifiesallthis(disregardtheLoginComponentimportfornow,aswewilluseitinthenextsection):
app/app.component.spec.ts
importAppComponentfrom'./app.component';
import{LoginComponent}from'./login/login';
import{provide}from'@angular/core';
import{
describe,
expect,
it,
inject,
beforeEach,
beforeEachProviders}from'@angular/core/testing';
import{
Router,
RouteRegistry,
ROUTER_PRIMARY_COMPONENT}from'@angular/router-deprecated';
import{Location}from'@angular/common';
import{SpyLocation}from'@angular/common/testing';
import{RootRouter}from'@angular/router-deprecated';
describe('AppComponent',()=>{
letlocation:Location,router:Router;
//WeoverridetheRouterandLocationprovidersanditsown
//dependenciesinordertoinstantiateafixturerouterto
//triggerroutingactionsandalocationspy
beforeEachProviders(()=>[
RouteRegistry,
provide(Location,{useClass:SpyLocation}),
provide(Router,{useClass:RootRouter}),
provide(ROUTER_PRIMARY_COMPONENT,{useValue:AppComponent})
]);
//WeinstantiateRouterandLocationobjectsbeforeeachtest
beforeEach(inject([Router,Location],(_router,_location)=>{
router=_router;
location=_location;
}));
//Specswithassertions
it('cannavigatetothemaintaskscomponent',done=>{
www.EBooksWorld.ir
//Wenavigatetoacomponentandchecktheresulting
//stateintheURL
router.navigate(['TasksComponent'])
.then(()=>{
expect(location.path()).toBe('/tasks');
done();
})
.catch(e=>done.fail(e));
});
});
www.EBooksWorld.ir
TestingroutesbyURLInourpreviousexample,wetestedhowwecouldnavigatetoanamedrouteandcheckiftherouterresultingURLmatchedtheexpectedstate.Butsometimeswewanttocheckwhetherwehaveactuallyloadedthecomponentweaimedtoreach.Thefollowingexampletakestheoppositeapproach:wenavigatetoaURLandchecktheresultingcomponenttype.DoyourememberweimportedtheLoginComponenttokenpreviously?Now,we'llputittogooduseinournexttestassertion.
Pleaseappendthefollowingspectoapp/app.component.spec.ts:
...
it('shouldbeabletonavigatetothelogincomponent',done=>{
//WenavigatetoanURLandchecktheresultingstateintheURL
router.navigateByUrl('/login').then(()=>{
expect(router.currentInstruction.component.componentType)
.toBe(LoginComponent);
done();
}).catch(e=>done.fail(e));
});
www.EBooksWorld.ir
TestingredirectionsWhatifwewanttocheckwhetheraredirectionactuallyworks?Noworries,wejustneedtonavigatebyURLtothepathtriggeringtheredirectionandthencheckeitherthetypeoftheroutercurrentinstructionortheresultinglocationpath.
Pleaseappendthefollowingspectoapp/app.component.spec.ts:
it('shouldredirect"/"requeststothetaskscomponent',done=>{
//WenavigatetoanURLandchecktheresultingstateintheURL
router.navigateByUrl('/').then(()=>{
expect(location.path()).toBe('/tasks');
done();
}).catch(e=>done.fail(e));
});
www.EBooksWorld.ir
TestingservicesServicesarealsoasubjectoftestinginourapplications.OneofthetraitsthatmakeservicessouniqueintheAngular-landisthattheydonotnecessarilyneedtorelyonAngular2itself.UnlessaserviceneedstotakeadvantageofAngular2'sDImachinerytoleverageotherAngularmodulessuchasHTTP,itwillbeprettymucharegular,framework-agnosticJavaScriptclass.
Thismakestestingserviceswithnodependenciesabreeze,where,justlikewedidwhentestingpipes,weneedtoinstantiatetheclassoneverytestspecandtestitspropertiesandthefunctionalityofitsmethods.
Let'sseeallthisthroughanactualexample,wherewewilltestthesimplestservicewehaveonstore:
app/shared/services/settings.service.spec.ts
importSettingsServicefrom'./settings.service';
import{
describe,
expect,
it,
inject,
beforeEach,
beforeEachProviders}from'@angular/core/testing';
describe('shared:SettingsService',()=>{
letsettingsService:SettingsService;
beforeEach(()=>{
settingsService=newSettingsService();
});
it('shouldprovidethedurationforeachpomodoro',()=>{
expect(settingsService.timerMinutes).toBeDefined();
expect(settingsService.timerMinutes).toBeGreaterThan(0);
expect(settingsService.timerMinutes).not.toBeNaN();
});
it('shouldprovidepluralmappingsfortasks',()=>{
consttasksPluralMappings=settingsService.pluralsMap['tasks'];
constactualProperties=Object.keys(tasksPluralMappings).sort()
constexpectedProperties=['=0','=1','other'].sort();
expect(tasksPluralMappings).toBeDefined();
expect(actualProperties).toEqual(expectedProperties);
});
});
Here,wearetestingwhetherthereisanactualnumericvalueconfiguredinthetimerMinutespropertyandwhetherthetasksPluralMappingscontainsallthemappingsweexpectitto
www.EBooksWorld.ir
have.Wecould(anddefinitelyshould)conductmoretests,butforthetimebeingitisfinetoleavethetestlikethisandthenfocusonanimportantdetail.IfwehadwantedtoleveragetheAngular2testingmachineryforthistest,wecouldhaveinstantiatedtheSettingServiceobjectlikethis:
beforeEachProviders(()=>[SettingsService]);
beforeEach(inject([SettingsService],
(_settingsService:SettingsService)=>{
settingsService=_settingsService;
}
));
Thissyntaxwillremindyouofwhatyousawintheprevioussections.Ultimately,taketheapproachthatsuitsyourcodingstyle.Obviously,thelatterwillrequirelessrefactoringasourserviceclassevolvesandbeginstodemandinjecteddependencies,inwhichcasemakinguseofthebeforeEachProviders()functionwillbecomeparamount.
www.EBooksWorld.ir
TestingasynchronousservicesThepreviousexampleshowcasedhowwecantestthemostbasicbare-bonesservicewecancomeupwith,butinrealitytwoofthemostcommontraitsofcustomservicesarethattheyusuallyrelyonotherdependenciestoprovidetheirfunctionality.Inaddition,thesefunctionalitiesaremostofthetimebasedonasynchronousmethodsthatconnecttothirdpartyservices.Regardlessoftheinterfaceexposedbytheseasynchronousmembers(callbacks,emittedevents,promises,orobservables),testingservicesliketheseisnothardatall,aswecanseeinthefollowingexamplewherewetestthedifferentAPIendpointsofAuthenticationService.Thecodeisasfollows:
app/shared/services/authentication.service.spec.ts
importAuthenticationServicefrom'./authentication.service';
import{
describe,
expect,
it,
inject,
beforeEach,
beforeEachProviders}from'@angular/core/testing';
describe('shared:AuthenticationService',()=>{
letauthenticationService:AuthenticationService;
beforeEachProviders(()=>[
AuthenticationService
]);
beforeEach(inject(
[AuthenticationService],(_authenticationService)=>{
authenticationService=_authenticationService;
}
));
it('shouldrejectinvalidcredentials',done=>{
authenticationService.login({
username:'foo',
password:'bar'})
.then(success=>{
expect(success).toBeFalsy();
done();
});
});
describe('emitsaneventuponuserauthstatuschanges',()=>{
it('thatshouldbetruthyforsuccessfullogins',done=>{
authenticationService
.userIsloggedIn
.subscribe((authStatus:boolean)=>{
expect(authStatus).toBeTruthy();
done();
www.EBooksWorld.ir
});
authenticationService.login({
username:'john.doe@mail.com',
password:'letmein'
});
});
it('thatshouldbefalsyforfailedlogins',done=>{
authenticationService
.userIsloggedIn
.subscribe((authStatus:boolean)=>{
expect(authStatus).toBeFalsy();
done();
});
authenticationService.login({
username:'foo',
password:'bar'
});
});
});
});
Thetestmustseemlengthy,butitisactuallyquitesimple,sincewearejustputtingintopracticeallthatweknowbynowaboutunittesting.Theonlypartworthremarkingishowwewraptheexpectationassertioninthesubscribe()methodoftheuserIsLoggedIneventemittermember,sotheassertionwillonlybeevaluatedonceaneventisemittedandshinesthroughthesubscriptionfunction.Thecodeisasfollows:
authenticationService
.userIsloggedIn
.subscribe((authStatus:boolean)=>{
expect(authStatus).toBeTruthy();
done();
});
Wethenconductanauthenticationrequestinthefollowingblock,sothesubscribe()functionemitstheexpectedevent:
authenticationService.login({
username:'john.doe@mail.com',
password:'letmein'
});
Lastbutnotleast,pleasenoticehowwehavenestedadescribe()suitewithinanotherdescribe()suite.Thisisquitecommonwheneveritmakessensetogrouptestspecsbyareaoffunctionality,easingthetaskofdisablingtestsifrequired.
www.EBooksWorld.ir
MockingHttpresponseswithMockBackendThepreviousexampleisabitcontrivedsinceourAuthenticationServicemodulewasinfactamockbyitself,withnorealimplementationwhatsoever.Inarealscenario,theAuthenticationServiceshouldimplementanasynchronousmethodsendingaPOSTrequesttoanauthenticationservice.Let'supdateourcurrentserviceimplementationtoincludethisfeatureunderadifferentmethodname,soitdoesnotcollidewiththecurrentlogin()implementationanditstests.Thecodeisasfollows:
app/shared/services/authentication.service.ts
...
httpLogin(credentials):Promise<boolean>{
returnnewPromise(resolve=>{
consturl='/api/authentication';//OryourownAPIAuthurl
constbody=JSON.stringify(credentials);
constheaders=newHeaders({'Content-Type':'application/json'});
constoptions=newRequestOptions({headers:headers});
this.http.post(url,body,options)
.map(response=>response.json())
.subscribe(authResponse=>{
letvalidCredentials:boolean=false;
if(authResponse&&authResponse.token){
validCredentials=true;
window.sessionStorage.setItem(
'token',
authResponse.token
);
}
this.userIsloggedIn.emit(validCredentials);
resolve(validCredentials);
},
error=>console.log(error)
);
});
}
...
InournewimplementationofAuthenticationService,anewasynchronousmethodnamedhttpLogin()performsanactualHTTPPOSTrequesttoanauthserviceofourchoiceandsubmitsthecredentialsinJSONformat,resolvingapromisewithaBooleanvalueafterpersistingthetokeninthelocalsessionstorage.
Tip
Forthesakeofreusability,themethodshouldjustresolvetotheHTTPresponseoncefetchedanditwillbeuptothemethod'sclientstodecidehowtopersisttheinformationcontainedin
www.EBooksWorld.ir
theresponseandwhattodonext.Forthesakeofbrevity,let'sleaveourmethodlikethis.
Withournewshinyasynchronousmethod,therearesomenewchallenges:
First,weneedtodeclarethedependenciesrequiredforallowingHTTPconnectionsSecond,ournewmethodperformsanactualHTTPrequesttoaremoteservicewhichisoutofthescopeofourtestingcapabilities,soweneedtobeabletointerceptsuchrequestandreturnacustomizedmockedresponsetofulfilourtests
Regardingtheformer,wealreadysawinprevioussectionshowtodeclareprovidersandinstantiateanHttpdependencythroughtheinjectorusingMockBackendinthedependencyconstructor.
However,itistimetoharnessallthefunctionalitythatMockBackendcanprovideforinterceptingHTTPconnectionsandmockresponsesofourown.Let'sgetbacktoauthentication.service.spec.tsandreplacethecurrentimplementationofbeforeEachProviders()andbeforeEach()afterimportingsomemoretokensyou'realreadyfamiliarwith:
app/shared/services/authentication.service.spec.ts(updated)
importAuthenticationServicefrom'./authentication.service';
import{provide}from'@angular/core';
import{
describe,
expect,
it,
inject,
beforeEach,
beforeEachProviders}from'@angular/core/testing';
import{
Http,
BaseRequestOptions,
Response,
ResponseOptions}from'@angular/http';
import{MockBackend,MockConnection}from'@angular/http/testing';
import'rxjs/add/operator/map';
describe('shared:AuthenticationService',()=>{
letauthenticationService:AuthenticationService;
letmockBackend:MockBackend;
beforeEachProviders(()=>[
MockBackend,
BaseRequestOptions,
provide(Http,{
useFactory:(
backend:MockBackend,
options:BaseRequestOptions
)=>{
returnnewHttp(backend,options);
},
www.EBooksWorld.ir
deps:[MockBackend,BaseRequestOptions]
}),
AuthenticationService
]);
beforeEach(inject(
[MockBackend,AuthenticationService],
(_mockBackend,_authenticationService)=>{
authenticationService=_authenticationService;
mockBackend=_mockBackend;
}
));
it('canfetchavalidtokenwhenqueryingtheAuthAPI',done=>{
constmockedResponse=newResponseOptions({
body:'{"token":"eyJhbGciOi"}'
});
mockBackend.connections.subscribe(
(connection:MockConnection)=>{
if(connection.request.url==='/api/authentication'){
connection.mockRespond(newResponse(mockedResponse));
}
}
);
authenticationService.httpLogin({
username:'foo',
password:'bar'
}).then(success=>{
expect(success).toBeTruthy();
done();
},
error=>done.fail(error)
);
});
//Restoftestspecsremainthesamebelow
//...
First,weimporteverythingthatisrequiredtointeractwithHTTP-basedservices.ThebeforeEachProviders()andthebeforeEach()implementationshavenodifferencewithwhatwealreadysawwhenoverviewingtimer-widget.component.test.ts.PerhapstheonlynuanceworthremarkingisthefactthatwebindanewinstanceofMockBackendtothemockBackendvariableoneverytestexecution.Itisrequiredbecausewewillbeusingitinthenewlyintroducedtestspec.Let'sreviewitinmoredetail.First,wedefineamockedresponsewithsomefakedata.WewillbeusingthismockresponselateronwhenperformingactualHTTPrequests:
constmockedResponse=newResponseOptions({
body:'{"token":"eyJhbGciOi"}'
});
TheMockBackendobjectsexposeaconnectionspropertyoftypeEventEmitter,whichemitsa
www.EBooksWorld.ir
MockConnectioneventobjecteverytimeitdetectsanattempttoperformaXHRconnectionthroughHttp(which,asweknownow,isusingmockBackendtoperformbackendconnections).ThisMockConnectionobjectcontainsrelevantinformationabouttherequestattemptthatwecanusetorefineourtestandreturnaspecificresponsetailoredtothenecessitiesofourtestingscenario.Todoso,wewillusethemockRespond()oftheconnectionobjectitself.Thecodeisasfollows:
mockBackend.connections.subscribe(
(connection:MockConnection)=>{
if(connection.request.url==='/api/authentication'){
connection.mockRespond(newResponse(mockedResponse));
}
}
);
Withalltheseelementsinplace,performinganactualrequestwithourservicemethodsandevaluatingtheresponsesbecomesaneasytask:
authenticationService.httpLogin({
username:'foo',
password:'bar'
}).then(success=>{
expect(success).toBeTruthy();
done();
},
error=>done.fail(error)
);
www.EBooksWorld.ir
TestingdirectivesThelastlegofourjourneyintotheworldofunittestingAngular2elementswillcoverdirectives.Directiveswillbeusuallyquitestraightforwardintheiroverallshape,beingprettymuchcomponentswithnoviewattached.Thefactthatdirectivesusuallyworkwithcomponentsgivesusaverygoodideaofhowtoproceedwhentestingthem.
Wecouldcreateastubcomponentforthepurposeofthetestandthenbindthedirectiveonit,eitherdirectlyupondefiningitorbyleveragingtheoverrideDirectives()oftheTestComponentBuilderinstanceobjectwewillcomposeforthetest.Inthatsense,thecomponent,asthehostelementforthedirective,willproxyourtestoperations,sowewillnotdelvedeeperintothisapproachafterreviewingcomponenttestingintheprevioussections.
Anotherapproachistoleveragethehostbindingsandlistenersourdirectivetakesactionon,andtesttheboundmethodstothesedecoratorstoseeiftheyprovidethefunctionalityrequired.Let'slookatanactualexampleofthisapproachbytestingtheTaskTooltipDirectivemodule:
app/tasks/task-tooltip.directive.spec.ts
import{Task}from'../shared/shared';
importTaskTooltipDirectivefrom'./task-tooltip.directive';
import{
describe,
expect,
it,
beforeEach}from'@angular/core/testing';
describe('shared:TaskTooltipDirective',()=>{
lettaskTooltipDirective:TaskTooltipDirective;
beforeEach(()=>{
taskTooltipDirective=newTaskTooltipDirective();
});
it('shouldupdateagiventooltipuponmouseover',done=>{
letmockTooltip={innerText:''};
taskTooltipDirective.task=<Task>{name:'Foo'};
taskTooltipDirective.taskTooltip=mockTooltip;
taskTooltipDirective.onMouseOver();
expect(mockTooltip.innerText).toBe('Foo');
done();
});
it('shouldrestoreagiventooltipuponmouseout',done=>{
letmockTooltip={innerText:'Foo'};
taskTooltipDirective.task=<Task>{name:'Bar'};
taskTooltipDirective.taskTooltip=mockTooltip;
www.EBooksWorld.ir
taskTooltipDirective.onMouseOver();
expect(mockTooltip.innerText).toBe('Bar');
taskTooltipDirective.onMouseOut();
expect(mockTooltip.innerText).toBe('Foo');
done();
});
});
Here,weinstantiatethedirectiveobjectdirectly,sinceithasnodependenciesthatrequireustousetheinjectorinstead.Sincealltheoperationsperformedbythisdirectivearegovernedbytheclassmethodsdecoratedwith@HostListener()decorators,wejustneedtofeedthedirectiveclassinputmemberswithmockdataandseeifweobtainthedesiredbehavior.
www.EBooksWorld.ir
TheroadaheadThislasttestexamplewrapsupourjourneyintounittestingwithAngular2,butkeepinmindthatwehavebarelyscratchedthesurface.TestingwebapplicationsingeneralandAngular2applicationsinparticularposesamyriadofscenariosthatneedaspecificapproachmostofthetimes.Rememberthatifaspecifictestrequiresacumbersomeandconvolutedsolution,weareprobablyfacingagoodcaseforamoduleredesigninstead.
Whereshouldwegofromhere?ThereareseveralpathstocompoundourknowledgeofwebapplicationtestinginAngular2andbecomegreattestingninjas.
www.EBooksWorld.ir
UsingJasmineincombinationwithKarmaSofar,wehaveusedtheJasmineHTMLspecrunnertoexecuteourtestsandgetaresultsreport.Whilethisisperfectlyfineforsmallerprojects,theHTMLspecrunnermightnotbethebestsolutionforbiggerprojects,especiallyifwewantourteststobere-executedautomaticallywhencodechangesorweneedtohookupourtestslayerwithacontinuousintegrationserver.
Atthispointyouwillwanttouseamorepowerfulandfasterspecrunnerwithoutcompromisingyourproject.Forthatreason,yourbestbetmightbepickingupKarmaasaspecrunner.UsedbytheAngularteamitself,itplayswellwithJasmineandothertestingframeworkssuchasMochaorQUnit.Italsofeaturesasimplebutpowerfulconfigurationsetupwithsupportforautomaticspecscanning,filewatching,multiplereportoutputs,andadvancedextensibilitywithplugins.
Forthoseinneedtohookuptheirapplicationwithcontinuousintegrationservers,KarmaalsoprovidesadaptersforJenkins,Travis,orSemaphore.
Youcanfindfurtherinformationathttps://karma-runner.github.io.
www.EBooksWorld.ir
IntroducingcodecoveragereportsinyourteststackHowcanweknowhowfardoourtestsgoontestingtheapplication?Arewesurewearenotleavinganypieceofcodeuntestedandifso,isitrelevant?Howcanwedetectthepiecesofcodethatfalloutsidethescopeofourcurrenttestssowecanbetterassessiftheyareworthtestingornot?
Theseconcernscanbeeasilyaddressedbyintroducingcodecoveragereportinginourapplicationtestsstack.Acodecoveragetoolaimstotrackdownthescopeofourunittestinglayerandproduceaneducatedreportinformingoftheoverallreachofyourtestspecsandwhatpiecesofcodestillremainuncovered.
Thereareseveraltoolsforimplementingcodecoverageanalysisinourapplications,Blanket(http://blanketjs.org),andIstanbul(https://gotwarlost.github.io/istanbul)themostpopularonesatthistime.Inbothcases,theinstallationprocessisprettyquickandeasy.
www.EBooksWorld.ir
ImplementingE2EtestsInthischapter,wesawhowwecouldtestcertainpartsoftheUIbyevaluatingthestateoftheDOM.Thisgivesusagoodideaofhowthingswouldlooklikefromtheenduser'spointofview,butultimatelythisisjustanuneducatedguess.
End-to-end(E2E)testingisamethodologyfortestingwebapplicationsusinganautomatedagentthatwillprogrammaticallyfollowtheenduser'sflowfromstarttofinish.Contrarytowhatunittestingposes,thenuancesofthecodeimplementationarenotrelevanthere,sinceE2Etestingentailstestingourapplicationfromstarttofinishfromtheuser'sendpoint.Thisapproachallowsustotesttheapplicationinanintegratedway.Whileunittestingfocusesonthereliabilityofeachparticularpieceofthepuzzle,E2Etestingdoesassesstheintegrityofthepuzzleasawhole,findingintegrationissuesbetweencomponentsthatarefrequentlyoverlookedbyunittests.
TheAngularteambuiltforthepreviousincarnationoftheAngularframeworkapowerfultoolnamedProtractor(https://docs.angularjs.org/guide/e2e-testing),whichisdefinedasfollows:
"..anendtoendtestrunnerwhichsimulatesuserinteractionsthatwillhelpyouverifythehealthofyourAngularapplication."
ThetestssyntaxwillbecomeprettyfamiliarsinceitalsousesJasmineforputtingtogethertestspecs.Unfortunately,E2Esitsoutsidethescopeofthisbook,butthereareseveralresourcesyoucanrelyontoexpandyourknowledgeonthesubject.Inthatsense,werecommendthebookAngular2Test-drivendevelopment,PacktPublishing,whichprovidesbroadinsightsontheuseofProtractortocreateE2EtestsuitesforourAngular2applications.
www.EBooksWorld.ir
SummaryWeareattheendofourjourney,andit'sbeenalongbutexcitingonewithoutanyshadeofdoubt.Inthischapter,yousawtheimportanceofintroducingunittestinginourAngular2applications,thebasicshapeofaunittest,andtheprocessofsettingupJasmineforourtests.Youalsosawhowtocodepowerfultestsforourcomponents,directives,pipes,routes,andservices.WealsodiscussednewchallengesinyourpathformasteringAngular2.Itisfairtosaythatthereisstillalongroadahead,anditisdefinitelyanexitingone.
Theendofthischapterisalsotheendofthisbook,buttheexperiencecontinuesbeyonditsboundaries.Angular2isstillaprettyyoungframeworkandassuchallthegreatthingsthatitwillbringtothecommunityareyettobecreated.Hopefully,youwillbeoneofthosecreators.Ifso,pleaselettheauthorknow.
Thanksfortakingthetimeforreadingthisbook.
www.EBooksWorld.ir
IndexA
Angular2defining/Afreshstart,Hello,Angular2!URL/SettingupourworkspaceTypeScriptclasses/TypeScriptclassesmetadatadecorators,defining/IntroducingmetadatadecoratorsTypeScript,compilingintobrowser-friendlyJavaScript/CompilingTypeScriptintobrowser-friendlyJavaScriptHTMLcontainer/TheHTMLcontainerexamples/Servingtheexamplesofthisbooktemplate,editing/Puttingeverythingtogetherdirectives/DirectivesinAngular2dependencyinjection,defining/HowdependencyinjectionworksinAngular2dependencies,injectingacrosscomponenttree/Injectingdependenciesacrossthecomponenttreeproviders,overridingininjectorshierarchy/Overridingprovidersintheinjectorhierarchyinjectorsupport,extendingtocustomentities/Extendinginjectorsupporttocustomentitiesapplications,initializingwithbootstrap()/Initializingapplicationswithbootstrap()references/IntroducingthePomodoroAppdirectorystructurematcherfunctions,defining/Angular2custommatcherfunctions
Angular2cheatsheetURL/Someextrasyntacticsugarwhenbindingexpressions
Angular2componentsdefining/DivingdeeperintoAngular2componentsproductivity,improving/Improvingproductivitycomponentmethods/Componentmethodsanddataupdatesdataupdates/Componentmethodsanddataupdatesinteractivity,adding/Addinginteractivitytothecomponentdataoutput,improvinginview/ImprovingthedataoutputintheviewandpolishingtheUI
Angular2routerbundleimplementing/AddingsupportfortheAngular2router
AnimationBuildercomponents,animatingwith/AnimatingcomponentswiththeAnimationBuilderCssAnimationBuilderAPI/TheCssAnimationBuilderAPIanimationstate,trackingwithAnimationclass/TrackinganimationstatewiththeAnimationclass
animationscreating,withplainvanillaCSS/CreatinganimationswithplainvanillaCSS
www.EBooksWorld.ir
handling,withCSSclasshooks/HandlinganimationwithCSSclasshooksapplication
bootstrapping/Bootstrappingtheapplicationapplication,Angular2
refactoring/RefactoringourapplicationtheAngular2wayapplications,withbootstrap()
defining/Initializingapplicationswithbootstrap()switching,betweendevelopmentandproductionmodes/SwitchingbetweendevelopmentandproductionmodesAngular2built-inchangedetectionprofiler,enabling/EnablingAngular2'sbuilt-inchangedetectionprofiler
applications,withmodulesorganizing/Organizingourapplicationswithmodulesinternalmodules/Internalmodulesexternalmodules/Externalmodules
Arrayabout/Array
asynchronousinformationhandling,strategiesused/Strategiesforhandlingasynchronousinformation
Asyncpipe/TheasyncpipeAtScript
about/DecoratorsinTypeScript
www.EBooksWorld.ir
BBlanket
URL/IntroducingcodecoveragereportsinyourteststackBootstrap/Installingdependenciesbootstrapmethod
actions,defining/Puttingeverythingtogether
www.EBooksWorld.ir
CCanDeactivaterouterhook
bypassing,uponformsubmission/BypassingtheCanDeactivaterouterhookuponsubmittingforms
childroutersdefining/Definingchildrouterslinking,tochildroutes/Linkingtochildroutes
classanatomydefining/Anatomyofaclass–constructors,properties,methods,getters,andsetters
classdecoratorfunctionsignatureextending/Extendingtheclassdecoratorfunctionsignature
classdecoratorsabout/Classdecorators
classesabout/Classes,interfaces,andclassinheritance
classhooksabout/Classhooksavailabledefining/Classhooksavailable
classinheritanceabout/Classes,interfaces,andclassinheritanceclasses,extendingwith/Extendingclasseswithclassinheritance
classstatementelements/Anatomyofaclass–constructors,properties,methods,getters,andsetters
clientauthenticationservicemocking/Mockingaclientauthenticationserviceexposing,toothercomponents/Exposingournewservicetoothercomponentsunauthorisedaccess,blocking/Blockingunauthorizedaccessuserauthenticationstatus,reflectingonUI/MakingtheUIreactivetotheuserauthenticationstatus
codecoveragereportsdefining,inteststack/Introducingcodecoveragereportsinyourteststack
componentscreating/Creatingourcomponentstimercontext/Thetimercontexttaskscontext/Thetaskscontexttoprootcomponent,defining/Definingthetoprootcomponenttesting/Testingcomponentspropertiesandmethods/Testingcomponentstesting,withdependencies/Testingcomponentswithdependenciescomponentdependencies,overridingforrefinedtesting/Overridingcomponentdependenciesforrefinedtesting
www.EBooksWorld.ir
componenttreedefining/Introducingthecomponenttree
ControlGroupsabout/Controls,ControlGroups,andtheFormBuilderclasscontrolgroups,definingwith/DefiningcontrolgroupsimperativelywithControlGroup
controlinteractiontracking/Trackingcontrolinteractionandvalidatinginputchanges,trackingwithlocalreferences/Trackingchangeswithlocalreferences
Controlsabout/Controls,ControlGroups,andtheFormBuilderclass,IntroducingControlsandValidatorscreating,inDOMwithngControldirective/ControlsintheDOM–thengControldirectivegrouping,inDOMwithNgControlGroupdirective/GroupingcontrolsintheDOMwithNgControlGroupDOMandcontroller,connectingwithngFormModel/ConnectingtheDOMandthecontrollerwithngFormModel
CSSclasshooksanimation,handlingwith/HandlinganimationwithCSSclasshooks
CSSspecificityURL/Managingviewencapsulation
CSSstylingencapsulating/EncapsulatingCSSstylingstylesproperty/ThestylespropertystyleUrlsproperty/ThestyleUrlspropertyinlinestylesheets/Inlinestylesheetsviewencapsulation,managing/Managingviewencapsulation
currencypipe/Thecurrencypipecustomanimationdirectives
developing/Developingcustomanimationdirectivesinteracting,fromtemplate/Interactingwithourdirectivefromthetemplate
customdirectivesbuilding/Buildingourowncustomdirectivesanatomy/Anatomyofacustomdirectivetasktooltipcustomdirective,building/Buildingatasktooltipcustomdirectivenamingconventions/Awordaboutnamingconventionsforcustomdirectivesandpipes
customelementsnaming/Componentmethodsanddataupdates
customeventsused,forcommunicatingbetweencomponents/Communicatingbetweencomponentsthroughcustomevents
custompipes
www.EBooksWorld.ir
building/Buildingourowncustompipesanatomy/Anatomyofacustompipeformattimeoutput,improving/Acustompipetobetterformattimeoutputdata,filtering/Filteringoutdatawithcustomfilters
customvaluessettingup/Settingupcustomvaluesdeclaratively
www.EBooksWorld.ir
Ddatepipe/Thedatepipedecorators,TypeScript
classdecorators/Classdecoratorspropertydecorators/Propertydecoratorsmethoddecorators/Methoddecoratorsparameterdecorators/Parameterdecorators
dependenciesabout/HowdependencyinjectionworksinAngular2
dependencyinjectionrestricting/Restrictingdependencyinjectiondownthecomponenttree
directivesabout/DirectivesinAngular2coredirectives/CoredirectivesNgIf/NgIfNgFor/NgForNgStyle/NgStyleNgClass/NgClassNgSwitch/NgSwitch,NgSwitchWhen,andNgSwitchDefaultNgSwitchWhen/NgSwitch,NgSwitchWhen,andNgSwitchDefaultNgSwitchDefault/NgSwitch,NgSwitchWhen,andNgSwitchDefaulttesting/Testingdirectives
documentation,AngularURL/TheResponseobject
domainspecificlanguage(DSL)about/LookingintothefuturewithngAnimate2.0
www.EBooksWorld.ir
EE2Etests
implementing/ImplementingE2Etestsabout/ImplementingE2Etests
ECMAScript6(ES6orES2015)about/UnderstandingthecaseforTypeScript
Enumabout/Enum
executionflowabout/Functions,lambdas,andexecutionflow
www.EBooksWorld.ir
Ffacademodule
creating/Creatingafacademoduleincludingacustomprovidersbarrelform
type,bindingwithNgModeldirective/BindingatypetoaformwithNgModelFormBuilderclass
about/Controls,ControlGroups,andtheFormBuilderclassfunctionparameters,TypeScript
optionalparameters/Optionalparametersdefaultparameters/Defaultparametersrestparameters/Restparametersfunctionsignature,overloading/Overloadingthefunctionsignature
functionsabout/Functions,lambdas,andexecutionflowtypes,annotating/Annotatingtypesinourfunctions
www.EBooksWorld.ir
GGenerics
references/MethoddecoratorsGulp
URL/LeveragingGulpwithotherIDEs
www.EBooksWorld.ir
HHeadersclass
URL/TheResponseobjectHTMLcontainer
tasklisttablebuilding,Angulardirectivesused/BuildingourtasklisttablewithAngulardirectives
HttpAPIdefining/IntroducingtheHTTPAPIRequestclass,using/WhentousetheRequestandRequestOptionsArgsclassesRequestOptionsArgsclass,using/WhentousetheRequestandRequestOptionsArgsclassesResponseobject/TheResponseobjecterrors,handlingwhenperformingHttprequests/HandlingerrorswhenperformingHttprequestsHttpclass,injecting/InjectingtheHttpclassandtheHTTP_PROVIDERSmodulessymbolHTTP_PROVIDERSmodulesymbol/InjectingtheHttpclassandtheHTTP_PROVIDERSmodulessymbol
www.EBooksWorld.ir
II18npipes/Thei18npipesI18nPluralpipe/Thei18nPluralpipeI18nSelectpipe/Thei18nSelectpipeIDE
enhancing/EnhancingourIDESublimeText3/SublimeText3Atom/AtomVisualStudioCode/VisualStudioCodeWebStorm/WebStormGulp,leveragingwithotherIDEs/LeveragingGulpwithotherIDEs
injectionabout/HowdependencyinjectionworksinAngular2
inputvalidating/Trackingcontrolinteractionandvalidatinginput
interfacesabout/Classes,interfaces,andclassinheritance
IstambulURL/Introducingcodecoveragereportsinyourteststack
www.EBooksWorld.ir
JJasmine
URL/Dependencyinjectioninunittestsusing,withKarma/UsingJasmineincombinationwithKarma
JohnPapaURL/IntroducingthePomodoroAppdirectorystructure
JSBINURL/Observablesinanutshell
Jsonpipe/TheJSONpipe
www.EBooksWorld.ir
KKarma
URL/UsingJasmineincombinationwithKarma
www.EBooksWorld.ir
Llambdas
about/Functions,lambdas,andexecutionflowlocalreferences
used,fortrackingcontrolchanges/Trackingchangeswithlocalreferenceslogincomponent
building/Arealexample–ourlogincomponentloginfeaturecontext/Theloginfeaturecontextloginformtemplate/Theloginformtemplateimplementation/Thelogincomponentcustomvalidation,applyingtocontrols/Applyingcustomvalidationtoourcontrolsstatechanges,monitoringincontrols/Watchingstatechangesinourcontrolsaccessmanagement,handling/RunningtheextramileonaccessmanagementcustomsecureRouterOutletdirective,building/BuildingourownsecureRouterOutletdirective
lowercasepipe/Theuppercase/lowercasepipe
www.EBooksWorld.ir
Nnamedkeyframe
about/CreatinganimationswithplainvanillaCSSngAnimate2.0
defining/LookingintothefuturewithngAnimate2.0about/LookingintothefuturewithngAnimate2.0
NgClassdirective/NgClassngControldirective
Controls,creatinginDOM/ControlsintheDOM–thengControldirectiveNgControlGroupdirective
Controls,groupinginDOM/GroupingcontrolsintheDOMwithNgControlGroupNgFordirective/NgForNgIfdirective/NgIfNgModeldirective
about/TheNgModeldirectivetype,bindingtoform/BindingatypetoaformwithNgModelCanDeactivaterouterhook,bypassinguponformsubmission/BypassingtheCanDeactivaterouterhookuponsubmittingforms
NgStyledirective/NgStyleNgSwitchDefaultdirective
about/NgSwitch,NgSwitchWhen,andNgSwitchDefaultNgSwitchdirective
about/NgSwitch,NgSwitchWhen,andNgSwitchDefaultNgSwitchWhendirective
about/NgSwitch,NgSwitchWhen,andNgSwitchDefaultNode.js
URL/SettingupourworkspaceNPMmoduleofficialrepository
references/Servingtheexamplesofthisbooknumberpipe/Thenumberpipe
www.EBooksWorld.ir
OObservabledata
serving,throughHTTP/Arealcasestudy–servingObservabledatathroughHTTPtasks,addingtotasksservice/Addingtaskstoourtasksservice
observablesinnutshell/Observablesinanutshell
www.EBooksWorld.ir
Ppercentpipe/Thepercentpipepipes
templatebindings,manipulatingwith/ManipulatingtemplatebindingswithPipesUppercase/lowercasepipe/Theuppercase/lowercasepipeabout/Thenumber,percent,andcurrencypipesnumberpipe/Thenumberpipepercentpipe/Thepercentpipecurrencypipe/Thecurrencypipeslicepipe/Theslicepipedatepipe/ThedatepipeJsonpipe/TheJSONpipereplacepipe/ThereplacepipeI18npipes/Thei18npipesI18nPluralpipe/Thei18nPluralpipeI18nSelectpipe/Thei18nSelectpipeAsyncpipe/Theasyncpipenamingconventions/Awordaboutnamingconventionsforcustomdirectivesandpipestesting/Testingpipes
playgroundpageURL/Anatomyofaclass–constructors,properties,methods,getters,andsetters
PomodoroAppdirectorystructuredefining/IntroducingthePomodoroAppdirectorystructure
PomodorotasklistHTMLcontainer,setting/SettingupourmainHTMLcontainer
Pomodorotasklistabout/PuttingitalltogetherinthePomodorotasklisttasks,toggling/Togglingtasksinourtaskliststatechangesintemplates,displaying/Displayingstatechangesinourtemplateschildcomponents,embedding/Embeddingchildcomponents
PomodorotechniqueURL/Improvingproductivity
ProtractorURL/ImplementingE2Etests
providerslookuprestricting/Restrictingproviderlookup
www.EBooksWorld.ir
RReactivefunctionalprogramming
inAngular2/ReactivefunctionalprogramminginAngular2RxJSlibrary/TheRxJSlibrary
replacepipe/Thereplacepiperouteparameters
handling/Handlingrouteparametersdynamicparameters,passing/Passingdynamicparametersinourroutesparsing,withRouteParamsservice/ParsingrouteparameterswiththeRouteParamsservice
Routerlifecyclehooksabout/TheRouterlifecyclehooksCanActivatehook/TheCanActivatehookOnActivatehook/TheOnActivateHookCanDeactivatehook/TheCanDeactivateandOnDeactivatehooksOnDeactivatehook/TheCanDeactivateandOnDeactivatehooksCanReusehook/TheCanReuseandOnReusehooksOnReusehook/TheCanReuseandOnReusehookstipsandtricks/Advancedtipsandtricksredirecting,tootherroutes/Redirectingtootherroutesbasepath,tweaking/TweakingthebasepathgeneratedURLs,finetuningwithlocationstrategies/FinetuningourgeneratedURLswithlocationstrategiescomponents,loadingasynchronouslywithAsyncRoutes/LoadingcomponentsasynchronouslywithAsyncRoutes
routerservicenewcomponent,buildingfordemonstration/BuildinganewcomponentfordemonstrationpurposesRouteConfigdecorator,configuringwithRouteDefinitioninstances/ConfiguringtheRouteConfigdecoratorwiththeRouteDefinitioninstancesrouterdirectives,defining/Therouterdirectives–RouterOutletandRouterLinkroutes,triggering/TriggeringroutesimperativelyCSShooks,foractiveroutes/CSShooksforactiveroutes
routestesting/Testingroutestesting,byURL/TestingroutesbyURLredirections,testing/Testingredirections
RxJSlibraryabout/TheRxJSlibrary
www.EBooksWorld.ir
Sscalableapplications
conventions,defining/Commonconventionsforscalableapplicationsfileandmodulenamingconventions/Fileandmodulenamingconventionsseamlessscalability,ensuringwithfacadesorbarrels/Ensuringseamlessscalabilitywithfacadesorbarrels
servicestesting/Testingservicesasynchronousservices,testing/TestingasynchronousservicesHttpresponses,mockingwithMockBackend/MockingHttpresponseswithMockBackend
sharedcontextdefining/Thesharedcontextservices/Servicesinthesharedcontextapplicationsettings,configuringfromcentralservice/Configuringapplicationsettingsfromacentralservice
singleresponsibilityprincipleabout/RefactoringourapplicationtheAngular2way
slicepipe/Theslicepipestaticvalidatormethods
about/IntroducingControlsandValidatorsrequired/IntroducingControlsandValidatorsminLength(minLength-number)/IntroducingControlsandValidatorsmaxLength(maxLength-number)/IntroducingControlsandValidatorspattern(pattern-string)/IntroducingControlsandValidatorscompose(validators-Function[])/IntroducingControlsandValidatorscomposeAsync()/IntroducingControlsandValidators
strategiesused,forhandlingasynchronousinformation/Strategiesforhandlingasynchronousinformation
stringtypeabout/Stringvariables,declaring/DeclaringourvariablestheECMAScript6way
www.EBooksWorld.ir
Ttemplate
configuring,fromcomponentclass/Configuringourtemplatefromourcomponentclassinternaltemplate/Internalandexternaltemplatesexternaltemplate/Internalandexternaltemplates
templatebindingsmanipulating,withpipes/ManipulatingtemplatebindingswithPipes
templatesyntaxabout/Abettertemplatesyntaxdatabindings,withinputproperties/Databindingswithinputpropertiesexpressions,binding/Someextrasyntacticsugarwhenbindingexpressionseventbinding,withoutputproperties/Eventbindingwithoutputpropertiesinputandoutputproperties/Inputandoutputpropertiesinactiondata,emittingthroughcustomevents/Emittingdatathroughcustomeventslocalreferences,intemplates/Localreferencesintemplatesalternativesyntax,forinputandoutputproperties/Alternativesyntaxforinputandoutputproperties
testenvironmentsettingup/Settingupourtestenvironmenttestrunner,implementing/ImplementingourtestrunnerNPMcommands,settingup/SettingupNPMcommands
testsdebugging/Testingcomponents
TitleclassURL/TheOnActivateHook
transitionpropertiesabout/CreatinganimationswithplainvanillaCSS
two-waydatabindingabout/Two-waydatabindinginAngular2NgModeldirective/TheNgModeldirective
TypeScriptusing,overothersyntaxes/WhyTypeScriptoverothersyntaxes?about/WhyTypeScriptoverothersyntaxes?case,definingfor/UnderstandingthecaseforTypeScriptbenefits/ThebenefitsofTypeScripttypes/TypesinTypeScriptstring/Stringnumber/Numberboolean/Booleandynamictyping,withanytype/Dynamictypingwiththeanytypevoid/Voidtypeinference/Typeinference
www.EBooksWorld.ir
functionparameters/FunctionparametersinTypeScriptscopehandling,withlambdas/Betterfunctionsyntaxandscopehandlingwithlambdasinterfaces/InterfacesinTypeScriptdecorators/DecoratorsinTypeScriptreferences/DecoratorsinTypeScriptdefining/Theroadahead
TypeScriptcompilerwikiURL/InstallingTypeScript
TypeScriptofficialsiteURL/TheTypeScriptofficialsite
TypeScriptpluginURL/SublimeText3
TypeScriptresourcesdefining/IntroducingTypeScriptresourcesinthewildTypeScriptofficialsite/TheTypeScriptofficialsiteTypeScriptWiki/TheTypeScriptWiki
TypeScriptWikiURL/TheTypeScriptWiki
www.EBooksWorld.ir
Uunittest
about/Whydoweneedtests?defining,inAngular2/PartsofaunittestinAngular2dependencyinjection,defining/Dependencyinjectioninunittests
uppercasepipe/Theuppercase/lowercasepipe
www.EBooksWorld.ir
VValidatorsclass
about/IntroducingControlsandValidatorsViewEncapsulationenum
values/Managingviewencapsulation
www.EBooksWorld.ir
WWebcomponents
defining/Webcomponentstemplates/WebcomponentsCustomElements/WebcomponentsShadowDOM/WebcomponentsHTMLImports/Webcomponents
WebPackreference/Installingdependencies
workspacesetting/Settingupourworkspacedependencies,installing/InstallingdependenciesTypeScript,installing/InstallingTypeScriptproperties/InstallingTypeScriptTypeScripttypings,installing/InstallingTypeScripttypings
www.EBooksWorld.ir
top related