dl.ebooksworld.irdl.ebooksworld.ir/motoman/packt.learning.angular.2.pdf · improving productivity...

Post on 03-Jun-2020

7 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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