angular 2 cookbook - programmer booksdowngrading angular 2 components to angular 1 directives with...

681

Upload: others

Post on 25-May-2020

29 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade
Page 2: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Angular2Cookbook

Page 3: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

TableofContents

Angular2CookbookCreditsAbouttheAuthorAbouttheReviewerwww.PacktPub.com

Whysubscribe?CustomerFeedbackDedicationPreface

WhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport

DownloadingtheexamplecodeErrataPiracyQuestions

1.StrategiesforUpgradingtoAngular2IntroductionComponentizingdirectivesusingcontrollerAsencapsulation

GettingreadyHowtodoit...Howitworks...There'smore...Seealso

MigratinganapplicationtocomponentdirectivesGettingreadyHowtodoit...Howitworks...There'smore...Seealso

ImplementingabasiccomponentinAngularJS1.5GettingreadyHowtodoit...Howitworks...There'smore...Seealso

Page 4: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NormalizingservicetypesGettingreadyHowtodoit...Howitworks...There'smore...Seealso

ConnectingAngular1andAngular2withUpgradeModuleGettingreadyHowtodoit...

ConnectingAngular1toAngular2Howitworks...There'smore...Seealso

DowngradingAngular2componentstoAngular1directiveswithdowngradeComponentGettingreadyHowtodoit...Howitworks...Seealso

DowngradeAngular2providerstoAngular1serviceswithdowngradeInjectableGettingreadyHowtodoit...Seealso

2.ConqueringComponentsandDirectivesIntroductionUsingdecoratorstobuildandstyleasimplecomponent

GettingreadyHowtodoit...

WritingtheclassdefinitionWritingthecomponentclassdecorator

Howitworks...There'smore...Seealso

PassingmembersfromaparentcomponentintoachildcomponentGettingreadyHowtodoit...

ConnectingthecomponentsDeclaringinputs

Howitworks...There'smore...

AngularexpressionsUnidirectionaldatabindingMembermethods

Page 5: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBindingtonativeelementattributes

Howtodoit...Howitworks...Seealso

RegisteringhandlersonnativebrowsereventsGettingreadyHowtodoit...Howitworks...There'smore...Seealso

GeneratingandcapturingcustomeventsusingEventEmitterGettingreadyHowtodoit...

CapturingtheeventdataEmittingacustomeventListeningforcustomevents

Howitworks...There'smore...Seealso

AttachingbehaviortoDOMelementswithdirectivesGettingreadyHowtodoit...

AttachingtoeventswithHostListenersHowitworks...There'smore...Seealso

ProjectingnestedcontentusingngContentGettingreadyHowtodoit...Howitworks...There'smore...Seealso

UsingngForandngIfstructuraldirectivesformodel-basedDOMcontrolGettingreadyHowtodoit...Howitworks...There'smore...Seealso

ReferencingelementsusingtemplatevariablesGettingreadyHowtodoit...

Page 6: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Seealso

AttributepropertybindingGettingreadyHowtodoit...Howitworks...There'smore...Seealso

UtilizingcomponentlifecyclehooksGettingreadyHowtodoit...Howitworks...There'smore...Seealso

ReferencingaparentcomponentfromachildcomponentGettingreadyHowtodoit...Howitworks...There'smore...Seealso

Configuringmutualparent-childawarenesswithViewChildandforwardRefGettingreadyHowtodoit...

ConfiguringaViewChildreferenceCorrectingthedependencycyclewithforwardRefAddingthedisablebehavior

Howitworks...There'smore...

ViewChildrenSeealso

Configuringmutualparent-childawarenesswithContentChildandforwardRefGettingreadyHowtodoit...

ConvertingtoContentChildCorrectingdatabinding

Howitworks...There'smore...

ContentChildrenSeealso

3.BuildingTemplate-DrivenandReactiveFormsIntroductionImplementingsimpletwo-waydatabindingwithngModel

Page 7: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Howitworks...There'smore...Seealso

ImplementingbasicfieldvalidationwithaFormControlGettingreadyHowtodoit...Howitworks...There'smore...

ValidatorsandattributedualityTaglesscontrols

SeealsoBundlingcontrolswithaFormGroup

GettingreadyHowtodoit...Howitworks...There'smore...

FormGroupvalidatorsErrorpropagation

SeealsoBundlingFormControlswithaFormArray

GettingreadyHowtodoit...Howitworks...There'smore...Seealso

ImplementingbasicformswithNgFormGettingreadyHowtodoit...

DeclaringformfieldswithngModelHowitworks...There'smore...Seealso

ImplementingbasicformswithFormBuilderandformControlNameGettingreadyHowtodoit...Howitworks...There'smore...Seealso

CreatingandusingacustomvalidatorGettingreadyHowtodoit...

Page 8: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...There'smore...

RefactoringintovalidatorattributesSeealso

CreatingandusingacustomasynchronousvalidatorwithPromisesGettingreadyHowtodoit...Howitworks...There'smore...

ValidatorexecutionSeealso

4.MasteringPromisesIntroductionUnderstandingandimplementingbasicPromises

GettingreadyHowtodoit...Howitworks...There'smore...

DecoupledandduplicatedPromisecontrolResolvingaPromisetoavalueDelayedhandlerdefinitionMultiplehandlerdefinitionPrivatePromisemembers

SeealsoChainingPromisesandPromisehandlers

Howtodoit...Chainedhandlers'datahandoffRejectingachainedhandler

Howitworks...There'smore...

Promisehandlertreescatch()

SeealsoCreatingPromisewrapperswithPromise.resolve()andPromise.reject()

Howtodoit...Promisenormalization

Howitworks...There'smore...Seealso

ImplementingPromisebarrierswithPromise.all()Howtodoit...Howitworks...

Page 9: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Seealso

CancelingasynchronousactionswithPromise.race()GettingreadyHowtodoit...Howitworks...Seealso

ConvertingaPromiseintoanObservableHowtodoit...Howitworks...There'smore...Seealso

ConvertinganHTTPserviceObservableintoaZoneAwarePromiseGettingreadyHowtodoit...Howitworks...Seealso

5.ReactiveXObservablesIntroduction

TheObserverPatternReactiveXandRxJSObservablesinAngular2ObservablesandPromises

BasicutilizationofObservableswithHTTPGettingreadyHowtodoit...Howitworks...

Observable<Response>TheRxJSmap()operatorSubscribe

There'smore...HotandcoldObservables

SeealsoImplementingaPublish-SubscribemodelusingSubjects

GettingreadyHowtodoit...Howitworks...There'smore...

NativeRxJSimplementationSeealso

CreatinganObservableauthenticationserviceusingBehaviorSubjectsGettingready

Page 10: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...InjectingtheauthenticationserviceAddingBehaviorSubjecttotheauthenticationserviceAddingAPImethodstotheauthenticationserviceWiringtheservicemethodsintothecomponent

Howitworks...There'smore...Seealso

BuildingageneralizedPublish-Subscribeservicetoreplace$broadcast,$emit,and$onGettingreadyHowtodoit...

IntroducingchannelabstractionHookingcomponentsintotheserviceUnsubscribingfromchannels

Howitworks...There'smore...

ConsiderationsofanObservable'scompositionandmanipulationSeealso

UsingQueryListsandObservablestofollowchangesinViewChildrenGettingreadyHowtodoit...

DealingwithQueryListsCorrectingtheexpressionchangederror

Howitworks...Hatetheplayer,notthegame

SeealsoBuildingafullyfeaturedAutoCompletewithObservables

GettingreadyHowtodoit...

UsingtheFormControlvalueChangesObservableDebouncingtheinputIgnoringserialduplicatesFlatteningObservablesHandlingunorderedresponses

Howitworks...Seealso

6.TheComponentRouterIntroductionSettingupanapplicationtosupportsimpleroutes

GettingreadyHowtodoit...

SettingthebaseURL

Page 11: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

DefiningroutesProvidingroutestotheapplicationRenderingroutecomponentswithRouterOutlet

Howitworks...There'smore...

InitialpageloadSeealso

NavigatingwithrouterLinksGettingreadyHowtodoit...Howitworks...There'smore...

RouteorderconsiderationsSeealso

NavigatingwiththeRouterserviceGettingreadyHowtodoit...Howitworks...There'smore...Seealso

SelectingaLocationStrategyforpathconstructionHowtodoit...There'smore...

ConfiguringyourapplicationserverforPathLocationStrategyBuildingstatefulroutebehaviorwithRouterLinkActive

GettingreadyHowtodoit...Howitworks...There'smore...Seealso

ImplementingnestedviewswithrouteparametersandchildroutesGettingreadyHowtodoit...

AddingaroutingtargettotheparentcomponentDefiningnestedchildviewsDefiningthechildroutesDefiningchildviewlinksExtractingrouteparameters

Howitworks...There'smore...

RefactoringwithasyncpipesSeealso

Page 12: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WorkingwithmatrixURLparametersandroutingarraysGettingreadyHowtodoit...Howitworks...There'smore...Seealso

AddingrouteauthenticationcontrolswithrouteguardsGettingreadyHowtodoit...

ImplementingtheAuthserviceWiringuptheprofileviewRestrictingrouteaccesswithrouteguardsAddingloginbehaviorAddingthelogoutbehavior

Howitworks...There'smore...

TheactualauthenticationSecuredataandviews

Seealso7.Services,DependencyInjection,andNgModule

IntroductionInjectingasimpleserviceintoacomponent

GettingreadyHowtodoit...Howitworks...There'smore...Seealso

ControllingserviceinstancecreationandinjectionwithNgModuleGettingreadyHowtodoit...

SplittinguptherootmoduleHowitworks...There'smore...

InjectingdifferentserviceinstancesintodifferentcomponentsServiceinstantiation

SeealsoServiceinjectionaliasingwithuseClassanduseExisting

GettingreadyDualservicesAunifiedcomponent

Howtodoit...Howitworks...

Page 13: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Refactoringwithdirectiveproviders

SeealsoInjectingavalueasaservicewithuseValueandOpaqueTokens

GettingreadyHowtodoit...Howitworks...There'smore...Seealso

Buildingaprovider-configuredservicewithuseFactoryGettingreadyHowtodoit...

DefiningthefactoryInjectingOpaqueTokenCreatingproviderdirectiveswithuseFactory

Howitworks...There'smore...Seealso

8.ApplicationOrganizationandManagementIntroductionComposingpackage.jsonforaminimumviableAngular2application

GettingreadyHowtodoit...

package.jsondependenciespackage.jsondevDependenciespackage.jsonscripts

SeealsoConfiguringTypeScriptforaminimumviableAngular2application

GettingreadyHowtodoit...

Declarationfilestsconfig.json

Howitworks...Compilation

There'smore...SourcemapgenerationSinglefilecompilation

SeealsoPerformingin-browsertranspilationwithSystemJS

GettingreadyHowtodoit...Howitworks...

Page 14: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Seealso

ComposingapplicationfilesforaminimumviableAngular2applicationGettingreadyHowtodoit...

app.component.tsapp.module.tsmain.tsindex.htmlConfiguringSystemJS

SeealsoMigratingtheminimumviableapplicationtoWebpackbundling

GettingreadyHowtodoit...

webpack.config.jsSeealso

IncorporatingshimsandpolyfillsintoWebpackGettingreadyHowtodoit...Howitworks...Seealso

HTMLgenerationwithhtml-webpack-pluginGettingreadyHowtodoit...Howitworks...Seealso

SettingupanapplicationwithAngularCLIGettingreadyHowtodoit...

RunningtheapplicationlocallyTestingtheapplication

Howitworks...ProjectconfigurationfilesTypeScriptconfigurationfilesTestconfigurationfilesCoreapplicationfilesEnvironmentfilesAppComponentfilesAppComponenttestfiles

There'smore...Seealso

9.Angular2Testing

Page 15: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionCreatingaminimumviableunittestsuitewithKarma,Jasmine,andTypeScript

GettingreadyHowtodoit...

WritingaunittestConfiguringKarmaandJasmineConfiguringPhantomJSCompilingfilesandtestswithTypeScriptIncorporatingWebpackintoKarmaWritingthetestscript

Howitworks...There'smore...Seealso

WritingaminimumviableunittestsuiteforasimplecomponentGettingreadyHowtodoit...

UsingTestBedandasyncCreatingaComponentFixture

Howitworks...Seealso

Writingaminimumviableend-to-endtestsuiteforasimpleapplicationGettingreadyHowtodoit...

GettingProtractorupandrunningMakingProtractorcompatiblewithJasmineandTypeScriptBuildingapageobjectWritingthee2etestScriptingthee2etests

Howitworks...There'smore...Seealso

UnittestingasynchronousserviceGettingreadyHowtodoit...Howitworks...There'smore...

TestingwithoutinjectionSeealso

UnittestingacomponentwithaservicedependencyusingstubsGettingreadyHowtodoit...

Stubbingaservicedependency

Page 16: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

TriggeringeventsinsidethecomponentfixtureHowitworks...Seealso

UnittestingacomponentwithaservicedependencyusingspiesGettingreadyHowtodoit...

SettingaspyontheinjectedserviceHowitworks...There'smore...Seealso

10.PerformanceandAdvancedConceptsIntroductionUnderstandingandproperlyutilizingenableProdModewithpureandimpurepipes

GettingreadyHowtodoit...

GeneratingaconsistencyerrorIntroducingchangedetectioncomplianceSwitchingonenableProdMode

Howitworks...There'smore...Seealso

WorkingwithzonesoutsideAngularGettingreadyHowtodoit...

ForkingazoneOverridingzoneeventswithZoneSpec

Howitworks...There'smore...

Understandingzone.run()Microtasksandmacrotasks

SeealsoListeningforNgZoneevents

zone.jsNgZoneGettingreadyHowtodoit...

DemonstratingthezonelifecycleHowitworks...

Theutilityofzone.jsSeealso

ExecutionoutsidetheAngularzoneHowtodoit...

Page 17: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...There'smore...Seealso

ConfiguringcomponentstouseexplicitchangedetectionwithOnPushGettingreadyHowtodoit...

ConfiguringtheChangeDetectionStrategyRequestingexplicitchangedetection

Howitworks...There'smore...Seealso

ConfiguringViewEncapsulationformaximumefficiencyGettingreadyHowtodoit...

EmulatedstylingencapsulationNostylingencapsulationNativestylingencapsulation

Howitworks...There'smore...Seealso

ConfiguringtheAngular2RenderertousewebworkersGettingreadyHowtodoit...Howitworks...There'smore...

OptimizingforperformancegainsCompatibilityconsiderations

SeealsoConfiguringapplicationstouseahead-of-timecompilation

GettingreadyHowtodoit...

InstallingAOTdependenciesConfiguringngcAligningcomponentdefinitionswithAOTrequirementsCompilingwithngcBootstrappingwithAOT

Howitworks...There'smore...

GoingfurtherwithTreeShakingSeealso

ConfiguringanapplicationtouselazyloadingGettingready

Page 18: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Howitworks...There'smore...

AccountingforsharedmodulesSeealso

Page 19: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Angular2Cookbook

Page 20: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Angular2CookbookCopyright©2017PacktPublishing

Allrightsreserved.Nopartofthisbookmaybereproduced,storedinaretrievalsystem,ortransmittedinanyformorbyanymeans,withoutthepriorwrittenpermissionofthepublisher,exceptinthecaseofbriefquotationsembeddedincriticalarticlesorreviews.

Everyefforthasbeenmadeinthepreparationofthisbooktoensuretheaccuracyoftheinformationpresented.However,theinformationcontainedinthisbookissoldwithoutwarranty,eitherexpressorimplied.Neithertheauthor,norPacktPublishing,anditsdealersanddistributorswillbeheldliableforanydamagescausedorallegedtobecauseddirectlyorindirectlybythisbook.

PacktPublishinghasendeavoredtoprovidetrademarkinformationaboutallofthecompaniesandproductsmentionedinthisbookbytheappropriateuseofcapitals.However,PacktPublishingcannotguaranteetheaccuracyofthisinformation.

Firstpublished:January2017

Productionreference:1160117

PublishedbyPacktPublishingLtd.

LiveryPlace

35LiveryStreet

Birmingham

B32PB,UK.

ISBN978-1-78588-192-3

www.packtpub.com

Page 21: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CreditsAuthor

MattFrisbie

ProjectCoordinator

RitikaManoj

Reviewer

PatrickGillespie

Proofreader

SafisEditing

AcquisitionEditor

VinayArgekar

Indexer

FrancyPuthiry

ContentDevelopmentEditor

ArunNadar

Graphics

KirkD'Penha

TechnicalEditor

VivekArora

ProductionCoordinator

DeepikaNaik

CopyEditor

GladsonMonteiro

CoverWork

DeepikaNaik

Page 22: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

AbouttheAuthorMattFrisbieiscurrentlyasoftwareengineeratGoogle.HewastheauthorofthePacktPublishingbestsellerAngularJSWebApplicationDevelopmentCookbookandalsohaspublishedseveralvideoseriesthroughO'Reilly.HeisactiveintheAngularcommunity,givingpresentationsatmeetupsanddoingwebcasts.

WritingabookonAngular2whiletheframeworkitselfwasunfinishedwasanimmenselychallengingendeavor.Fragmentedexamples,incompletedocumentation,andanascentdevelopercommunitywerejustahandfulofthemanyroadblocksIencounteredonthejourneytofinishingthistitle,anditwasonlybecauseofalegionofsupportersthatthisbookwasfinishedandwasabletodojusticetotheframework.

ThisbookwouldnothavebeenpossiblewithoutthetirelessworkofallthePacktstaffinvolved.I'dspecificallyliketothankArunNadar,VivekArora,MerwynD'Souza,andVinayArgekarfortheireditorialoversightandexpertise,aswellasPatrickGillespieforhisworkascontentreviewer.I'dalsoliketothankJordan,Zoey,Scott,andmyfamilyandfriendsforcheeringmeon.

Page 23: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

AbouttheReviewerPatrickGillespiehasbeenintosoftwaredevelopmentsince1996.Hehasbothabachelor'sandamaster'sdegreeincomputerscience.Inhissparetime,heenjoysphotography,spendingtimewithhisfamily,andworkingonvarioussideprojectsforhiswebsite(http://patorjk.com/).

Page 24: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

www.PacktPub.comForsupportfilesanddownloadsrelatedtoyourbook,pleasevisitwww.PacktPub.com.

DidyouknowthatPacktofferseBookversionsofeverybookpublished,withPDFandePubfilesavailable?YoucanupgradetotheeBookversionatwww.PacktPub.comandasaprintbookcustomer,youareentitledtoadiscountontheeBookcopy.Getintouchwithusatservice@packtpub.comformoredetails.

Atwww.PacktPub.com,youcanalsoreadacollectionoffreetechnicalarticles,signupforarangeoffreenewslettersandreceiveexclusivediscountsandoffersonPacktbooksandeBooks.

https://www.packtpub.com/mapt

Getthemostin-demandsoftwareskillswithMapt.MaptgivesyoufullaccesstoallPacktbooksandvideocourses,aswellasindustry-leadingtoolstohelpyouplanyourpersonaldevelopmentandadvanceyourcareer.

Page 25: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Whysubscribe?FullysearchableacrosseverybookpublishedbyPacktCopyandpaste,print,andbookmarkcontentOndemandandaccessibleviaawebbrowser

Page 26: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CustomerFeedbackThankyouforpurchasingthisPacktbook.Wetakeourcommitmenttoimprovingourcontentandproductstomeetyourneedsseriously—that'swhyyourfeedbackissovaluable.Whateveryourfeelingsaboutyourpurchase,pleaseconsiderleavingareviewonthisbook'sAmazonpage.Notonlywillthishelpus,moreimportantlyitwillalsohelpothersinthecommunitytomakeaninformeddecisionabouttheresourcesthattheyinvestintolearn.

Youcanalsoreviewforusonaregularbasisbyjoiningourreviewers'club.Ifyou'reinterestedinjoining,orwouldliketolearnmoreaboutthebenefitsweoffer,pleasecontactus:[email protected].

Page 27: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

DedicationTomygrandparents,RichardandMargery.Here'stoupholdingthefamilyhonor.

Page 28: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Preface"Everybodyhasaplanuntiltheygetpunchedinthemouth."-MikeTyson,undisputedheavyweightchampionboxer

Soonafteritscreationin2009,AngularJSgrewintoawidelypopularfoundationaltoolforbuildingfrontendapplications.Asyearsandreleaseswentby,andtheJavaScriptcommunitymatured,theworldofclient-sideprogrammingbroadenedbeyondwhatAngularwasoriginallydesignedfor.Itscaretakerstookstockanddecidedthatasweepingoverhauloftheframeworkwasinorder.

AngularJS,nowAngular1,stillexistsandwillbesupportedfortheyearstocome,butinitswakeliesAngular2—awhollydifferentanimalbuiltforthefutureofclient-sidecomputing.Angular2abandonsantipatternsbythefistfuland,instead,isreshapedintoapreciseandelegantsoftwareinstrument.Itembracestheimpendingrenaissanceofwebtechnologies,buildingatopES6,webcomponents,webworkers,TypeScript,andreactiveprogramming,tonameafew.Itbringsframeworkmodularitytonewheights,buildingitselfaroundtheconceptthatanymodularpieceofAngular2shouldbeeasilydiscardedorreplaced.Bestofall,Angular2offersabountifulcollectionofconfigurationandtoolingthatwillmakeyourapplicationsrunatbreakneckspeed.

Tomanydevelopers,Angular2isfrighteningbecausesomuchofitisnewandunfamiliar.ThisbookexiststoofferyouanapproachablepathtoafullunderstandingofAngular2,whatitoffers,andhowbesttouseit.Youwillfindbothsimpleexamplestosetafoundationalunderstanding,andcomplexdemonstrationstohintattheframework'spower.Thebookisorganizedintorecipesthatareindependentofeachother,soyouareabletojumpinatanypointandimmediatelybeginlearning.

Page 29: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WhatthisbookcoversThisbookisuptodateforthe2.4releaseandiscompatiblethroughthe4.0releaseaswell,anditdoesnothaveanycodebasedonthebetaorreleasecandidates.

Chapter1,StrategiesforUpgradingtoAngular2,isanoverviewofanumberofwaystomigrateanAngular1applicationtoAngular2.Althoughthereisnoone-size-fits-allupgradestrategy,youwillfindthattheserecipesdemonstratesomewaysthatwillallowyoutopreservealargeamountofyourexistingAngular1codebase.

Chapter2,ConqueringComponentsandDirectives,givesabroadanddeepsetofexamplesinvolvingwhatAngular2componentsareandhowtousethem.Angular2applicationsarebuiltentirelyofcomponents,andthischapteroffersyouatotalrundownoftheirrole.

Chapter3,BuildingTemplate-DrivenandReactiveForms,coversthereworkedAngular2formmodules.Angular2offersyoutwoprimarystylesoferectingformfeatures,andthischaptercoversbothofthemindepth.

Chapter4,MasteringPromises,showshowthePromiseobjecthasaroleinAngular2.AlthoughRxJShassubsumedsomeoftheusefulnessofPromises,theyarestillfirst-classcitizensinES6andstillplayacrucialrole.

Chapter5,ReactiveXObservables,givesyouacrashcourseinhowAngular2hasembracedreactiveprogramming.ItincludesrecipesthatdemonstratethebasicsofObservablesandSubjects,aswellasadvancedimplementationsthattakeRxJStoitslimits.

Chapter6,TheComponentRouter,takesyouthroughthetotallyreworkedroutingmoduleinAngular2.ItcoversbothroutingbasicsaswellasanarrayofadvancedroutingconceptsuniquetoAngular2.

Chapter7,Services,DependencyInjection,andNgModule,describesthenewandimproveddependencyinjectionandmodulestrategiesofAngular2.Itgivesyouallthepiecesyouneedtobreakyourapplicationintoindependentservicesandmodules,aswellasidealstrategiesforconnectingthosepiecestogether.

Chapter8,ApplicationOrganizationandManagement,isabroadoverviewofhowyoucanmanageyourAngular2applicationinsideandoutsidetheclient.Angular2introducesanumberoflayersofcomplexitythatrequireadvancedtooling,andthischapterwillguideyouthroughhowtoapproachthem.

Chapter9,Angular2Testing,willguideyouthroughbothhowtosetuptestsuitesforAngular2aswellashowtowritevarioustypesoftestsforthesesuites.Manydevelopersavoidtestingwhenlearningaframeworkanew,andthischaptergentlyguidesyouthroughAngular2'sexcellent

Page 30: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

testinfrastructure.

Chapter10,PerformanceandAdvancedConcepts,isacrashcourseonthedizzyingarrayofcomplexconceptsthatAngular2comeswithoutofthebox.Thischaptercoversprogramorganizationandarchitecture,frameworkfeaturesandtooling,aswellascompile-timeoptimizations.

Page 31: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WhatyouneedforthisbookEveryrecipeinthisbookisaccompaniedbyalinktothebook'scompanionsite,http://ngcookbook.herokuapp.com/.RecipesthatinvolvecodeexampleswillincludealinktoaliveexampleonPlunker.Thiswillallowyoutoinspectandtestcodeinrealtimewithouthavingtoworryaboutcompilation,localservers,oranythingofthatilk.Itmustbenoted,however,thatthissetupisonlyappropriateforexperimentationandshouldnotbeusedforuser-facingorproductionapplications.

Angular2comesinbothJavaScriptandTypeScriptflavors,butthisbookaimsdirectlyattheTypeScriptedition,sinceitissyntacticallysuperior(asyouwillsoonrealize).Forproperproductionapplications,TypeScriptwillbecompiledintoJavaScriptbeforeitisservedtothebrowser.Thewaythisbookaccomplishesthis(andmanyothercodepreparationtasks)isinsideaNode.jsinstallonyourlocalmachine.Node.jsincludestheNodePackageManager(npm),whichletsyouinstallandrunopensourceJavaScriptsoftwarefromthecommandline.

SomechaptersinthisbookwillrequirethatyouhaveNode.jsinstalledbeforerunningcommandsandlaunchingalocalserverortestsuite.Furthermore,itisrecommended(butnotrequired)thatyouinstalltheNodeVersionManagerontopofNode.js,whichwillmakemanagingyourinstalledpackagesmucheasier.

Page 32: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WhothisbookisforTheuniverseofAngular2learningmaterialsiscurrentlyfragmentedandgross.Thisbookisforbothbeginnerdeveloperslookingtosinktheirteethintoanewframework,aswellasadvanceddevelopersinterestedinroundingouttheirknowledgeofaframeworkthatembracesthecomingworldofwebtech.

Fornewerdevelopers,ingestingallthesenewtechnologiesatoncemayseemoverwhelming.Theorganizationandpaceofthisbookisdesignedsothattopicsaregraduallyintroduced,anddesigndecisionsandrationalesareexplained.Don'tworry,thisbookisstillforyou.

Page 33: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConventionsInthisbook,youwillfindanumberoftextstylesthatdistinguishbetweendifferentkindsofinformation.Herearesomeexamplesofthesestylesandanexplanationoftheirmeaning.

Codewordsintext,databasetablenames,foldernames,filenames,fileextensions,pathnames,dummyURLs,userinput,andTwitterhandlesareshownasfollows:"Karmareadsitsconfigurationoutofakarma.conf.jsfile."

Ablockofcodeissetasfollows:

<p>{{date}}</p>

<h1>{{title}}</h1>

<h3>Writtenby:{{author}}</h3>

Whenwewishtodrawyourattentiontoaparticularpartofacodeblock,therelevantlinesoritemsaresetinbold:

@Component({

selector:'article',

template:`

<p>{{currentDate|date}}</p>

<h1>{{title}}</h1>

<h3>Writtenby:{{author}}</h3>

`

})

Anycommand-lineinputoroutputiswrittenasfollows:

npminstallkarmajasmine-corekarma-jasmine--save-dev

npminstallkarma-cli-g

Newtermsandimportantwordsareshowninbold.

Note

Warningsorimportantnotesappearinaboxlikethis.

Tip

Tipsandtricksappearlikethis.

Page 34: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ReaderfeedbackFeedbackfromourreadersisalwayswelcome.Letusknowwhatyouthinkaboutthisbook-whatyoulikedordisliked.Readerfeedbackisimportantforusasithelpsusdeveloptitlesthatyouwillreallygetthemostoutof.Tosendusgeneralfeedback,[email protected],andmentionthebook'stitleinthesubjectofyourmessage.Ifthereisatopicthatyouhaveexpertiseinandyouareinterestedineitherwritingorcontributingtoabook,seeourauthorguideatwww.packtpub.com/authors.

Page 35: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CustomersupportNowthatyouaretheproudownerofaPacktbook,wehaveanumberofthingstohelpyoutogetthemostfromyourpurchase.

Page 36: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

DownloadingtheexamplecodeYoucandownloadtheexamplecodefilesforthisbookfromyouraccountathttp://www.packtpub.com.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.

Youcandownloadthecodefilesbyfollowingthesesteps:

1. Loginorregistertoourwebsiteusingyoure-mailaddressandpassword.2. HoverthemousepointerontheSUPPORTtabatthetop.3. ClickonCodeDownloads&Errata.4. EnterthenameofthebookintheSearchbox.5. Selectthebookforwhichyou'relookingtodownloadthecodefiles.6. Choosefromthedrop-downmenuwhereyoupurchasedthisbookfrom.7. ClickonCodeDownload.

Oncethefileisdownloaded,pleasemakesurethatyouunziporextractthefolderusingthelatestversionof:

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

ThecodebundleforthebookisalsohostedonGitHubathttps://github.com/PacktPublishing/Angular-2-Cookbook.Wealsohaveothercodebundlesfromourrichcatalogofbooksandvideosavailableathttps://github.com/PacktPublishing/.Checkthemout!

Page 37: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

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.

Page 38: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

PiracyPiracyofcopyrightedmaterialontheInternetisanongoingproblemacrossallmedia.AtPackt,wetaketheprotectionofourcopyrightandlicensesveryseriously.IfyoucomeacrossanyillegalcopiesofourworksinanyformontheInternet,pleaseprovideuswiththelocationaddressorwebsitenameimmediatelysothatwecanpursuearemedy.

Pleasecontactusatcopyright@packtpub.comwithalinktothesuspectedpiratedmaterial.

Weappreciateyourhelpinprotectingourauthorsandourabilitytobringyouvaluablecontent.

Page 39: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

QuestionsIfyouhaveaproblemwithanyaspectofthisbook,[email protected],andwewilldoourbesttoaddresstheproblem.

Page 40: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter1.StrategiesforUpgradingtoAngular2Thischapterwillcoverthefollowingrecipes:

ComponentizingdirectivesusingthecontrollerAsencapsulationMigratinganapplicationtocomponentdirectivesImplementingabasiccomponentinAngularJS1.5NormalizingservicetypesConnectingAngular1andAngular2withUpgradeModuleDowngradingAngular2componentstoAngular1directiveswithdowngradeComponentDowngradingAngular2providerstoAngular1serviceswithdowngradeInjectable

Page 41: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionTheintroductionofAngular2intotheAngularecosystemwillsurelybeinterpretedandhandleddifferentlyforalldevelopers.SomewillsticktotheirexistingAngular1codebases,somewillstartbrandnewAngular2codebases,andsomewilldoagradualorpartialtransition.

ItisrecommendedthatyoubecomefamiliarwiththebehaviorofAngular2componentsbeforeyoudiveintotheserecipes.ThiswillhelpyouframementalmodelsasyouadaptyourexistingapplicationstobemorecompliantwiththeAngular2style.

Page 42: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ComponentizingdirectivesusingcontrollerAsencapsulationOneoftheunusualconventionsintroducedinAngular1wastherelationshipbetweendirectivesandthedatatheyconsumed.Bydefault,directivesusedaninheritedscope,whichsuitedtheneedsofmostdevelopersjustfine.Whilethiswaseasytouse,ithadtheeffectofintroducingextradependenciesinthedirectives,andalsotheconventionthatdirectivesoftendidnotownthedatatheywereconsuming.Additionally,thedatainterpolatedinthetemplatewasunclearinrelationtowhereitwasbeingassignedorowned.

Angular2utilizescomponentsasthebuildingblocksoftheentireapplication.Thesecomponentsareclass-basedandarethereforeinsomewaysatoddswiththescopemechanismsofAngular1.Transitioningtoacontroller-centricdirectivemodelisalargesteptowardscompliancewiththeAngular2standards.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/8194.

Page 43: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyourapplicationcontainsthefollowingsetupthatinvolvesthenesteddirectivesthatsharedatausinganisolatescope:

[index.html]

<divng-app="articleApp">

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.directive('article',function(){

return{

controller:function($scope){

$scope.articleData={

person:{firstName:'Jake'},

title:'LesothoYachtClubMembershipBooms'

};

},

template:`

<h1>{{articleData.title}}</h1>

<attributionauthor="articleData.person.firstName">

</attribution>

`

};

})

.directive('attribution',function(){

return{

scope:{author:'='},

template:`<p>Writtenby:{{author}}</p>`

};

});

Page 44: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Thegoalistorefactorthissetupsothattemplatescanbeexplicitaboutwherethedataiscomingfromandsothatthedirectiveshaveownershipofthisdata:

[app.js]

angular.module('articleApp',[])

.directive('article',function(){

return{

controller:function(){

this.person={firstName:'Jake'};

this.title='LesothoYachtClubMembershipBooms';

},

controllerAs:'articleCtrl',

template:`

<h1>{{articleCtrl.title}}</h1>

<attribution></attribution>

`

};

})

.directive('attribution',function(){

return{

template:`<p>Writtenby:{{articleCtrl.author}}</p>`

};

});

Inthissecondimplementation,anywhereyouusethearticledata,youarecertainofitsorigin.Thisisbetter,butthechilddirectiveisstillreferencingtheparentcontroller,whichisn'tidealsinceitisintroducinganunneededdependency.Theattributiondirectiveinstanceshouldbeprovidedwiththedata,anditshouldinsteadinterpolatefromitsowncontrollerinstance:

[app.js]

angular.module('articleApp',[])

.directive('article',function(){

return{

controller:function(){

this.person={firstName:'Jake'};

this.title='LesothoYachtClubMembershipBooms';

},

controllerAs:'articleCtrl',

template:`

<h1>{{articleCtrl.title}}</h1>

<attributionauthor="articleCtrl.person.firstName">

</attribution>

`

};

})

.directive('attribution',function(){

return{

Page 45: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

controller:function(){},

controllerAs:'attributionCtrl',

bindToController:{author:'='},

template:`<p>Writtenby:{{attributionCtrl.author}}</p>`

};

});

Muchbetter!Youprovidethechilddirectivewithastand-incontrollerandgiveitanaliasintheattributionCtrltemplate.ItisimplicitlyboundtothecontrollerinstanceviabindToControllerinthesamewayyouwouldaccomplisharegularisolatescope;however,thebindingisdirectlyattributedtothecontrollerobjectinsteadofthescope.

Nowthatyouhaveintroducedthenotionofdataownership,supposeyouwanttomodifyyourapplicationdata.What'smore,youwantdifferentpartsofyourapplicationtobeabletomodifyit.Anaïveimplementationofthiswouldbesomethingasfollows:

[app.js]

angular.module('articleApp',[])

.directive('attribution',function(){

return{

controller:function(){

this.capitalize=function(){

this.author=this.author.toUpperCase();

}

},

controllerAs:'attributionCtrl',

bindToController:{author:'='},

template:`

<png-click="attributionCtrl.capitalize()">

Writtenby:{{attributionCtrl.author}}

</p>`

};

});

Thedesiredbehaviorisforyoutoclickontheauthor,anditwillbecomecapitalized.However,inthisimplementation,thearticlecontroller'sdataismodifiedintheattributioncontroller,whichdoesnotownit.Itispreferableforthecontrollerthatownsthedatatoperformtheactualmodificationandinsteadsupplyaninterfacethatanoutsideentity—here,theattributiondirective—coulduse:

[app.js]

angular.module('articleApp',[])

.directive('article',function(){

return{

controller:function(){

this.person={firstName:'Jake'};

this.title='LesothoYachtClubMembershipBooms';

this.capitalize=function(){

Page 46: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

this.person.firstName=

this.person.firstName.toUpperCase();

};

},

controllerAs:'articleCtrl',

template:`

<h1>{{articleCtrl.title}}</h1>

<attributionauthor="articleCtrl.person.firstName"

upper-case-author="articleCtrl.capitalize()">

</attribution>

`

};

})

.directive('attribution',function(){

return{

controller:function(){},

controllerAs:'attributionCtrl',

bindToController:{

author:'=',

upperCaseAuthor:'&'

},

template:`

<png-click="attributionCtrl.upperCaseAuthor()">

Writtenby:{{attributionCtrl.author}}

</p>`

};

});

Vastlysuperior!Youarestillabletonamespacewithintheclickbinding,buttheowningdirectivecontrollerisprovidingamethodtooutsideentitiesinsteadofjustgivingthemdirectdataaccess.

Page 47: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Whenacontrollerisspecifiedinthedirectivedefinitionobject,onewillbeexplicitlyinstantiatedforeachdirectiveinstancethatiscreated.Thus,itisnaturalforthiscontrollerobjecttoencapsulatethedatathatitownsandforittobedelegatedtheresponsibilityofpassingitsdatatothemembersthatrequireit.

Thefinalimplementationaccomplishesseveralthings:

Improvedtemplatenamespacing:Whenyouusethe$scopepropertiesthatspanmultipledirectivesornestings,youarecreatingascenariowheremultipleentitiescanmanipulateandreaddatawithoutbeingabletoconcretelyreasonaboutwhereitiscomingfromorwhatiscontrollingit.Improvedtestability:Ifyoulookateachofthedirectivesinthefinalimplementation,you'llfindtheyarenottoodifficulttotest.Theattributiondirectivehasnodependenciesotherthanwhatareexplicitlypassedtoit.Encapsulation:Introducingthenotionofdataownershipinyourapplicationaffordsyouamuchmorerobuststructure,betterreusability,andadditionalinsightandcontrolinvolvingpiecesofyourapplicationinteracting.Angular2style:Angular2usesthe@Inputand@Outputannotationsoncomponentdefinitions.Mirroringthisstylewillmaketheprocessoftransitioningtoanapplicationeasier.

Page 48: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Youwillnoticethat$scopehasbeenmadetotallyirrelevantintheseexamples.Thisisgoodasthereisnonotionof$scopeinAngular2,whichmeansyouareheadingtowardshavinganupgradeableapplication.Thisisnottosaythat$scopedoesnotstillhaveutilityinanAngular1application,andsurely,therearescenarioswherethisisunavoidable,likewith$scope.$apply().

However,thinkingabouttheapplicationpiecesinthiscomponentstylewillallowyoutobemoreadequatelypreparedtoadoptAngular2conventions.

Page 49: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoMigratinganapplicationtocomponentdirectivesdemonstrateshowtorefactorAngular1toacomponentstyleImplementingabasiccomponentinAngularJS1.5detailshowtowriteanAngular1componentNormalizingservicetypesgivesinstructiononhowtoalignyourAngular1servicetypesforAngular2compatibility

Page 50: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

MigratinganapplicationtocomponentdirectivesInAngular1,thereareseveralbuilt-indirectives,includingngControllerandngInclude,thatdeveloperstendtoleanonwhenbuildingapplications.Whilenotanti-patterns,usingthesefeaturesmovesawayfromhavingacomponent-centricapplication.

Allthesedirectivesareactuallysubsetsofcomponentfunctionality,andtheycanbeentirelyrefactoredout.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/1008/.

Page 51: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyourinitialapplicationisasfollows:

[index.html]

<divng-app="articleApp">

<ng-includesrc="'/press_header.html'"></ng-include>

<divng-controller="articleCtrlasarticle">

<h1>{{article.title}}</h1>

<p>Writtenby:{{article.author}}</p>

</div>

<scripttype="text/ng-template"

id="/press_header.html">

<divng-controller="headerCtrlasheader">

<strong>

AngularChronicle-{{header.currentDate|date}}

</strong>

<hr/>

</div>

</script>

</div>

[app.js]

angular.module('articleApp',[])

.controller('articleCtrl',function(){

this.title='FoodFightEruptsDuringDiplomaticLuncheon';

this.author='Jake';

})

.controller('headerCtrl',function(){

this.currentDate=newDate();

});

Note

NotethatthisexampleapplicationcontainsalargenumberofverycommonAngular1patterns;youcanseethengControllerdirectivessprinkledthroughout.Also,itusesanngIncludedirectivetoincorporateaheader.Keepinmindthatthesedirectivesarenotinappropriateforawell-formedAngular1application.However,youcandobetter,andthisinvolvesrefactoringtoacomponent-drivendesign.

Page 52: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Component-drivenpatternsdon'tneedtobefrighteninginappearance.Inthisexample(andforessentiallyallAngular1applications),youcandoacomponentrefactorwhileleavingtheexistingtemplatelargelyintact.

BeginwiththengIncludedirective.Movingthistoacomponentdirectiveissimple—itbecomesadirectivewithtemplateUrlsettothetemplatepath:

[index.html]

<divng-app="articleApp">

<header></header>

<divng-controller="articleCtrlasarticle">

<h1>{{article.title}}</h1>

<p>Writtenby:{{article.author}}</p>

</div>

<scripttype="text/ng-template"

id="/press_header.html">

<divng-controller="headerCtrlasheader">

<strong>

AngularChronicle-{{header.currentDate|date}}

</strong>

<hr/>

</div>

</script>

</div>

[app.js]

angular.module('articleApp',[])

.controller('articleCtrl',function(){

this.title='FoodFightEruptsDuringDiplomaticLuncheon';

this.author='Jake';

})

.controller('headerCtrl',function(){

this.currentDate=newDate();

})

.directive('header',function(){

return{

templateUrl:'/press_header.html'

};

});

Next,youcanalsorefactorngControllereverywhereitappears.Inthisexample,youfindtwoextremelycommonappearancesofngController.Thefirstisattheheadofthepress_header.htmltemplate,actingasthetop-levelcontrollerforthattemplate.Often,thisresultsinneedingasuperfluouswrapperelementjusttohousetheng-controllerattribute.ThesecondisngControllernestedinsideyourprimaryapplicationtemplate,controllingsomearbitraryportionoftheDOM.Bothofthesecanberefactoredtocomponentdirectivesby

Page 53: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

reassigningngControllertoadirectivecontroller:

[index.html]

<divng-app="articleApp">

<header></header>

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.directive('header',function(){

return{

controller:function(){

this.currentDate=newDate();

},

controllerAs:'header',

template:`

<strong>

AngularChronicle-{{header.currentDate|date}}

</strong>

<hr/>

`

};

})

.directive('article',function(){

return{

controller:function(){

this.title='FoodFightEruptsDuringDiplomaticLuncheon';

this.author='Jake';

},

controllerAs:'article',

template:`

<h1>{{article.title}}</h1>

<p>Writtenby:{{article.author}}</p>

`

};

});

Tip

Notethattemplateshereareincludedinthedirectiveforvisualcongruity.Forlargeapplications,itispreferredthatyouusetemplateUrlandlocatethetemplatemarkupinitsownfile.

Page 54: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Generallyspeaking,anapplicationcanberepresentedbyahierarchyofnestedMVCcomponents.ngIncludeandngControlleractassubsetsofacomponentfunctionality,andsoitmakessensethatyouareabletoexpandthemintofullcomponentdirectives.

Intheprecedingexample,theultimateapplicationstructureiscomprisedofonlycomponents.Eachcomponentisdelegateditsowntemplate,controller,andmodel(byvirtueofthecontrollerobjectitself).SticklerswilldisputewhetherornotAngularbelongstotrueMVCstyle,butinthecontextofcomponentrefactoring,thisisirrelevant.Here,youhavedefinedastructurethatiscompletelymodular,reusable,testable,abstractable,andeasilymaintainable.ThisisthestyleofAngular2,andthevalueofthisshouldbeimmediatelyapparent.

Page 55: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Analertdeveloperwillnoticethatnoattentionispaidtoscopeinheritance.Thisisadifficultproblemtoapproach,mostlybecausemanyofthepatternsinAngular1aredesignedforamishmashbetweenascopeandcontrollerAs.Angular2isbuiltaroundstrictinputandoutputbetweennestedcomponents;however,inAngular1,scopeisinheritedbydefault,andnesteddirectives,bydefault,haveaccesstotheirencompassingcontrollerobjects.

Thus,totrulyemulateanAngular2style,onemustconfiguretheirapplicationtoexplicitlypassdataandmethodstochildren,similartothecontrollerAsencapsulationrecipe.However,thisdoesnotprecludedirectdataaccesstoancestralcomponentdirectivecontrollers;itmerelywagsafingeratitsinceitaddsadditionaldependencies.

Page 56: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComponentizingdirectivesusingcontrollerAsencapsulationshowsyouasuperiormethodoforganizingAngular1directivesImplementingabasiccomponentinAngularJS1.5detailshowtowriteanAngular1componentNormalizingservicetypesgivesinstructiononhowtoalignyourAngular1servicetypesforAngular2compatibility

Page 57: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingabasiccomponentinAngularJS1.5The1.5releaseofAngularJSintroducedanewtool:thecomponent.Whileitisn'texactlysimilartotheconceptoftheAngular2component,itdoesallowyoutobuilddirective-stylepiecesinanexplicitlycomponentizedfashion.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/7756/.

Page 58: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyourapplicationhadadirectivedefinedasfollows:

[index.html]

<divng-app="articleApp">

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.directive('article',function(){

return{

controller:function(){

this.person={firstName:'Jake'};

this.title='PoliceBustIllegalSnailRacingRing';

this.capitalize=function(){

this.person.firstName=

this.person.firstName.toUpperCase();

};

},

controllerAs:'articleCtrl',

template:`

<h1>{{articleCtrl.title}}</h1>

<attributionauthor="articleCtrl.person.firstName"

upper-case-author="articleCtrl.capitalize()">

</attribution>

`

};

})

.directive('attribution',function(){

return{

controller:function(){},

controllerAs:'attributionCtrl',

bindToController:{

author:'=',

upperCaseAuthor:'&'

},

template:`

<png-click="attributionCtrl.upperCaseAuthor()">

Writtenby:{{attributionCtrl.author}}

</p>`

};

});

Page 59: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...SincethisapplicationisalreadyorganizedaroundthecontrollerAsencapsulation,youcanmigrateittousethecomponent()definitionintroducedintheAngular1.5release.

Componentsacceptanobjectdefinitionsimilartoadirective,buttheobjectdoesnotdemandtobereturnedbyafunction—anobjectliteralisallthatisneeded.ComponentsutilizethebindingspropertyinthisobjectdefinitionobjectinthesamewaythatbindToControllerworksfordirectives.Withthis,youcaneasilyintroducecomponentsinthisapplicationinsteadofdirectives:

[index.html]

<divng-app="articleApp">

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.component('article',{

controller:function(){

this.person={firstName:'Jake'};

this.title='PoliceBustIllegalSnailRacingRing';

this.capitalize=function(){

this.person.firstName=

this.person.firstName.toUpperCase();

};

},

controllerAs:'articleCtrl',

template:`

<h1>{{articleCtrl.title}}</h1>

<attributionauthor="articleCtrl.person.firstName"

upper-case-author="articleCtrl.capitalize()">

</attribution>`

})

.component('attribution',{

controller:function(){},

controllerAs:'attributionCtrl',

bindings:{

author:'=',

upperCaseAuthor:'&'

},

template:`

<png-click="attributionCtrl.upperCaseAuthor()">

Writtenby:{{attributionCtrl.author}}

</p>

`

});

Page 60: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Noticethatsinceyourcontroller-centricdataorganizationmatcheswhatacomponentdefinitionexpects,notemplatemodificationsarenecessary.Components,bydefault,willutilizeanisolatescope.What'smore,theywillnothaveaccesstothealiasofthesurroundingcontrollerobjects,somethingthatcannotbesaidforcomponent-styledirectives.Thisencapsulationisanimportantofferingofthenewcomponentfeature,asithasdirectparitytohowcomponentsoperateinAngular2.

Page 61: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Sinceyouhavenowentirelyisolatedeachindividualcomponent,thereisonlyasinglecontrollerobjecttodealwithineachtemplate.Thus,Angular1.5automaticallyprovidesaconvenientaliasforthecomponent'scontrollerobject,namely—$ctrl.ThisisprovidedwhetherornotacontrollerAsaliasisspecified.Therefore,afurtherrefactoringyieldsthefollowing:

[index.html]

<divng-app="articleApp">

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.component('article',{

controller:function(){

this.person={firstName:'Jake'};

this.title='PoliceBustIllegalSnailRacingRing';

this.capitalize=function(){

this.person.firstName=

this.person.firstName.toUpperCase();

};

},

template:`

<h1>{{$ctrl.title}}</h1>

<attributionauthor="$ctrl.person.firstName"

upper-case-author="$ctrl.capitalize()">

</attribution>

`

})

.component('attribution',{

controller:function(){},

bindings:{

author:'=',

upperCaseAuthor:'&'

},

template:`

<png-click="$ctrl.upperCaseAuthor()">

Writtenby:{{$ctrl.author}}

</p>

`

});

Page 62: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComponentizingdirectivesusingcontrollerAsencapsulationshowsyouasuperiormethodoforganizingAngular1directivesMigratinganapplicationtocomponentdirectivesdemonstrateshowtorefactorAngular1toacomponentstyleNormalizingservicetypesgivesinstructiononhowtoalignyourAngular1servicetypesforAngular2compatibility

Page 63: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NormalizingservicetypesAngular1developerswillbequitefamiliarwiththefactory/service/providertrifecta.Inmanyways,thishasgonelargelyunalteredinAngular2conceptually.However,intheinterestofupgradinganexistingapplication,thereisonethingthatshouldbedonetomakethemigrationaseasyaspossible:eliminatefactoriesandreplacethemwithservices.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/5637/.

Page 64: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadasimpleapplicationasfollows:

[index.html]

<divng-app="articleApp">

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.factory('ArticleData',function(){

vartitle='IncumbentSenatorOustedbyStalkofBroccoli';

return{

getTitle:function(){

returntitle;

},

author:'Jake'

};

})

.component('article',{

controller:function(ArticleData){

this.title=ArticleData.getTitle();

this.author=ArticleData.author;

},

template:`

<h1>{{$ctrl.title}}</h1>

<p>Writtenby:{{$ctrl.author}}</p>

`

});

Page 65: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Angular2isclass-based,anditincludesitsservicetypesaswell.Theexampleheredoesnothaveaservicetypethatiscompatiblewithaclassstructure.Soitmustbeconverted.Thankfully,thisisquiteeasytodo:

[index.html]

<divng-app="articleApp">

<article></article>

</div>

[app.js]

angular.module('articleApp',[])

.service('ArticleData',function(){

vartitle='IncumbentSenatorOustedbyStalkofBroccoli';

this.getTitle=function(){

returntitle;

};

this.author='Jake';

})

.component('article',{

controller:function(ArticleData){

this.title=ArticleData.getTitle();

this.author=ArticleData.author;

},

template:`

<h1>{{$ctrl.title}}</h1>

<p>Writtenby:{{$ctrl.author}}</p>

`

});

Page 66: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Youstillwanttokeepthenotionoftitleprivate,butyoualsowanttomaintaintheAPIthattheinjectedservicetypeisproviding.Servicesaredefinedbyafunctionthatactsasaconstructor,andaninstancecreatedfromthisconstructoriswhatisultimatelyinjected.Here,youaresimplymovinggetTitle()andauthortobedefinedonthethiskeyword,whichtherebymakesitapropertyonallinstances.Notethattheuseinthecomponentandtemplatedoesnotchangeinanyway,anditshouldn't.

Page 67: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Thesimplesttoimplementservicetypes,Angular1factorieswereoftenusedfirstbymanydevelopers,includingmyself.Somedevelopersmighttakeoffenseatthefollowingclaim,butIdon'tthinktherewaseveragoodreasonforbothfactoriesandservicestoexist.Bothhaveahighdegreeofredundancy,andifyoudigdownintotheAngularsourcecode,youwillseethattheyessentiallyconvergetothesamemethods.

Whyservicesoverfactoriesthen?ThenewworldofJavaScript,ES6,andTypeScriptisbeingbuiltaroundclasses.Theyareafarmoreelegantwayofexpressingandorganizinglogic.Angular1servicesareanimplementationofprototype-basedclasses,whichwhenusedcorrectlyfunctioninessentiallythesamewayasformalES6/TypeScriptclasses.Ifyoustophere,youwillhavemodifiedyourservicestobemoreextensibleandcomprehensible.Ifyouintendtoupgrade,youwillfindthatAngular1serviceswillcleanlyupgradetoAngular2services.

Page 68: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComponentizingdirectivesusingcontrollerAsencapsulationshowsyouasuperiormethodfororganizingAngular1directivesMigratinganapplicationtocomponentdirectivesdemonstrateshowtorefactorAngular1toacomponentstyleImplementingabasiccomponentinAngularJS1.5detailshowtowriteanAngular1component

Page 69: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConnectingAngular1andAngular2withUpgradeModuleAngular2comeswiththeabilitytoconnectittoanexistingAngular1application.ThisisobviouslyadvantageoussincethiswillallowyoutoutilizeexistingcomponentsandservicesinAngular1intandemwithAngular2'scomponentsandservices.UpgradeModuleisthetoolthatissupportedbyAngularteamstoaccomplishsuchafeat.

Note

Thecode,links,andaliveexampleinrelationtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/4137/.

Page 70: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadaverysimpleAngular1applicationasfollows:

[index.html]

<!DOCTYPEhtml>

<html>

<head>

<!--Angular1scripts-->

<scriptsrc="angular.js"></script>

</head>

<body>

<divng-app="hybridApp"

ng-init="val='Angular1bootstrappedsuccessfully!'">

{{val}}

</div>

</body>

</html>

ThisapplicationinterpolatesavaluesetinanAngularexpressionsoyoucanvisuallyconfirmthattheapplicationhasbootstrappedandisworking.

Page 71: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbydeclaringthetop-levelangularmoduleinsideitsownfile.Insteadofusingascripttagtofetchtheangularmodule,requireAngular1,importit,andcreatetherootAngular1module:

[ng1.module.ts]

import'angular'

exportconstNg1AppModule=angular.module('Ng1AppModule',[]);

Angular2shipswithanupgrademoduleoutofthebox,whichisprovidedinsideupgrade.js.ThetwoframeworkscanbeconnectedwithUpgradeModule.

Note

ThisrecipeutilizesSystemJSandTypeScript,thespecificationsforwhichlieinsideaverycomplicatedconfigfile.Thisisdiscussedinalaterchapter,sodon'tworryaboutthespecifics.Fornow,youarefreetoassumethefollowing:

SystemJSisconfiguredtocompileTypeScript(.ts)filesontheflySystemJSisabletoresolvetheimportandexportstatementsinTypeScriptfilesSystemJSisabletoresolveAngular1and2libraryimports

Angular2requiresatop-levelmoduledefinitionaspartofitsbaseconfiguration:

[app/ng2.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{UpgradeModule}from'@angular/upgrade/static';

import{RootComponent}from'./root.component';

@NgModule({

imports:[

BrowserModule,

UpgradeModule,

],

bootstrap:[

RootComponent

],

declarations:[

RootComponent

]

})

exportclassNg2AppModule{

constructor(publicupgrade:UpgradeModule){}

}

exportclassAppModule{}

Page 72: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Tip

Thereasonwhythismoduledefinitionexiststhiswayisn'tcriticalforunderstandingthisrecipe.Angular2modulesarecoveredinChapter7,Services,DependencyInjection,andNgModule.

CreatetherootcomponentoftheAngular2application:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<p>Angular2bootstrappedsuccessfully!</p>

`

})

exportclassRootComponent{}

SinceAngular2willoftenbootstrapfromatop-levelfile,createthisfileasmain.tsandbootstraptheAngular2module:

[main.ts]

import{platformBrowserDynamic}

from'@angular/platform-browser-dynamic';

import{Ng1AppModule}from'./app/ng1.module';

import{Ng2AppModule}from'./app/ng2.module';

platformBrowserDynamic()

.bootstrapModule(Ng2AppModule);

ConnectingAngular1toAngular2

Don'tuseanng-apptobootstraptheAngular1application;instead,dothisafteryoubootstrapAngular2:

[main.ts]

import{platformBrowserDynamic}

from'@angular/platform-browser-dynamic';

import{Ng1AppModule}from'./app/ng1.module';

import{Ng2AppModule}from'./app/ng2.module';

platformBrowserDynamic()

.bootstrapModule(Ng2AppModule)

.then(ref=>{

ref.instance.upgrade

.bootstrap(document.body,[Ng1AppModule.name]);

});

Page 73: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Withthis,you'llbeabletoremoveAngular1'sJSscript,theng-appdirective,andaddintherootelementoftheAngular2app:

[index.html]

<!DOCTYPEhtml>

<html>

<head>

<!--Angular2scripts-->

<scriptsrc="zone.js"></script>

<scriptsrc="reflect-metadata.js"></script>

<scriptsrc="system.js"></script>

<scriptsrc="system-config.js"></script>

</head>

<body>

<divng-init="val='Angular1bootstrappedsuccessfully!'">

{{val}}

</div>

<root></root>

</body>

</html>

Note

ThenewscriptslistedherearedependenciesofanAngular2application,butunderstandingwhatthey'redoingisn'tcriticalforunderstandingthisrecipe.Thisisexplainedlaterinthebook.

Withallthis,youshouldseeyourAngular1applicationtemplatecompileandtheAngular2componentrenderproperlyagain.ThismeansthatyouaresuccessfullyrunningAngular1andAngular2frameworkssidebyside.

Page 74: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Makenomistake,whenyouuseUpgradeModule,youcreateanAngular1andAngular2apponthesamepageandconnectthemtogether.Thisadapterinstancewillallowyoutoconnectpiecesfromeachframeworkandusetheminharmony.

Morespecifically,thiscreatesanAngular1applicationatthetoplevelandallowsyoutousespiecesofanAngular2applicationinsideit.

Page 75: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Whileusefulforexperimentationandupgradingpurposes,thisshouldnotbeasolutionthatanyapplicationshouldrelyoninaproductioncontext.Youhaveeffectivelydoubledtheframeworkpayloadsizeandintroducedadditionalcomplexityinanexistingapplication.AlthoughAngular2isafarmoreperformantframework,donotexpecttohavethesamepristineresultswiththeUpgradeModulecross-pollination.

Thatsaid,asyouwillseeinsubsequentrecipes,youcannowuseAngular2componentsinanAngular1applicationusingtheadaptertranslationmethods.

Page 76: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoDowngradingAngular2componentstoAngular1directiveswithdowngradeComponentdemonstrateshowtouseanAngular2componentinsideanAngular1applicationDowngradeAngular2providerstoAngular1serviceswithdowngradeInjectable,whichdemonstrateshowtouseanAngular2serviceinsideanAngular1application

Page 77: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

DowngradingAngular2componentstoAngular1directiveswithdowngradeComponentIfyouhavefollowedthestepsinConnectingAngular1andAngular2withUpgradeModule,youshouldnowhaveahybridapplicationthatiscapableofsharingdifferentelementswiththeopposingframework.

Tip

IfyouareunfamiliarwithAngular2components,itisrecommendedthatyougothroughthecomponentschapterbeforeyouproceed.

ThisrecipewillallowyoutofullyutilizeAngular2componentsinsideanAngular1template.

Note

Thecode,links,andaliveexampleinrelationtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/1499/.

Page 78: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingAngular2componentthatyouwantedtouseinanAngular1application:

[app/article.component.ts]

import{Component,Input}from'@angular/core';

@Component({

selector:'ng2-article',

template:`

<h1>{{title}}</h1>

<p>Writtenby:{{author}}</p>

`

})

exportclassArticleComponent{

@Input()author:string

title:string='UnicycleJoustingRecognizedasOlympicSport';

}

BeginbycompletingtheConnectingAngular1andAngular2withUpgradeModulerecipe.

Page 79: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Angular1hasnocomprehensionofhowtoutilizeAngular2components.TheexistingAngular2frameworkwilldutifullyrenderitifgiventheopportunity,butthedefinitionitselfmustbeconnectedtotheAngular1frameworksothatitmayberequestedwhenneeded.

Beginbyaddingthecomponentdeclarationstothemoduledefinition;thisisusedtolinkthetwoframeworks:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{UpgradeModule}from'@angular/upgrade/static';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component';

@NgModule({

imports:[

BrowserModule,

UpgradeModule,

],

declarations:[

RootComponent,

ArticleComponent

],

bootstrap:[

RootComponent

]

})

exportclassNg2AppModule{

constructor(publicupgrade:UpgradeModule){}

}

ThisconnectsthecomponentdeclarationtotheAngular2context,butAngular1stillhasnoconceptofhowtointerfacewithit.Forthis,you'llneedtousedowngradeComponent()todefinetheAngular2componentasanAngular1directive.GivetheAngular1directiveadifferentHTMLtagtorenderinsidesoyoucanbecertainthatit'sAngular1doingtherenderingandnotAngular2:

[main.ts]

import{Component,Input}from'@angular/core';

import{downgradeComponent}from'@angular/upgrade/static';

import{Ng1AppModule}from'./ng1.module';

@Component({

selector:'ng2-article',

template:`

<h1>{{title}}</h1>

Page 80: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

<p>Writtenby:{{author}}</p>

`

})

exportclassArticleComponent{

@Input()author:string

title:string='UnicycleJoustingRecognizedasOlympicSport';

}

Ng1AppModule.directive(

'ng1Article',

downgradeComponent({component:ArticleComponent}));

Finally,sincethiscomponenthasaninput,you'llneedtopassthisvalueviaabindingattribute.EventhoughthecomponentisstillbeingdeclaredasanAngular1directive,you'llusetheAngular2bindingsyntax:

[index.html]

<!DOCTYPEhtml>

<html>

<head>

<!--Angular2scripts-->

<scriptsrc="zone.js"></script>

<scriptsrc="reflect-metadata.js"></script>

<scriptsrc="system.js"></script>

<scriptsrc="system-config.js"></script>

</head>

<body>

<divng-init="authorName='JakeHsu'">

<ng1-article[author]="authorName"></ng1-article>

</div>

<root></root>

</body>

</html>

Theinputandoutputmustbeexplicitlydeclaredatthetimeofconversion:

[app/article.component.ts]

import{Component,Input}from'@angular/core';

import{downgradeComponent}from'@angular/upgrade/static';

import{Ng1AppModule}from'./ng1.module';

@Component({

selector:'ng2-article',

template:`

<h1>{{title}}</h1>

<p>Writtenby:{{author}}</p>

`

})

exportclassArticleComponent{

@Input()author:string

Page 81: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

title:string='UnicycleJoustingRecognizedasOlympicSport';

}

Ng1AppModule.directive(

'ng1Article',

downgradeComponent({

component:ArticleComponent,

inputs:['author']

}));

Theseareallthestepsrequired.Ifdoneproperly,youshouldseethecomponentrenderalongwiththeauthor'snamebeinginterpolatedinsidetheAngular2componentthroughAngular1'sng-initdefinition.

Page 82: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...YouaregivingAngular1theabilitytodirectAngular2toacertainelementintheDOMandsay,"Ineedyoutorenderhere."Angular2stillcontrolsthecomponentviewandoperation,andineverysense,themainthingwereallycareaboutisafullAngular2componentadaptedforuseinanAngular1template.

Tip

downgradeComponent()takesanobjectspecifyingthecomponentasanargumentandreturnsthefunctionthatAngular1isexpectingforthedirectivedefinition.

Page 83: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoConnectingAngular1andAngular2withUpgradeModuleshowsyouhowtorunAngular1and2frameworkstogetherDowngradeAngular2providerstoAngular1serviceswithdowngradeInjectabledemonstrateshowtouseanAngular2serviceinsideanAngular1application

Page 84: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

DowngradeAngular2providerstoAngular1serviceswithdowngradeInjectableIfyouhavefollowedthestepsinConnectingAngular1andAngular2withUpgradeModule,youshouldnowhaveahybridapplicationthatiscapableofsharingdifferentelementswiththeopposingframework.IfyouareunfamiliarwithAngular2providers,itisrecommendedthatyougothroughthedependencyinjectionchapterbeforeyouproceed.

Likewithtemplatedcomponents,interchangeabilityisalsoofferedtoservicetypes.ItispossibletodefineaservicetypeinAngular2andtheninjectitintoanAngular1context.

Note

Thecode,links,andaliveexampleinrelationtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/2824/.

Page 85: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththecodewritteninConnectingAngular1andAngular2withUpgradeModule.

Page 86: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...First,definetheserviceyouwouldliketoinjectintoanAngular1component:

[app/article.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassArticleService{

article:Object={

title:'ResearchShowsMoonNotActuallyMadeofCheese',

author:'JakeHsu'

};

}

Next,definetheAngular1componentthatshouldinjectit:

[app/article.component.ts]

exportconstng1Article={

template:`

<h1>{{article.title}}</h1>

<p>{{article.author}}</p>

`,

controller:(ArticleService,$scope)=>{

$scope.article=ArticleService.article;

}

};

ArticleServicewon'tbeinjectedyetthough,sinceAngular1hasnoideathatthisserviceexists.Doingthisisverysimple,however.First,you'lllisttheserviceproviderintheAngular2moduledefinitionasyounormallywould:

[app/ng2.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{UpgradeModule}from'@angular/upgrade/static';

import{RootComponent}from'./root.component';

import{ArticleService}from'./article.service';

@NgModule({

imports:[

BrowserModule,

UpgradeModule,

],

declarations:[

RootComponent

],

providers:[

Page 87: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ArticleService

],

bootstrap:[

RootComponent

]

})

exportclassNg2AppModule{

constructor(publicupgrade:UpgradeModule){}

}

Still,Angular1doesnotunderstandhowtousetheservice.

InthesamewayyouconvertanAngular2componentdefinitionintoanAngular1directive,convertanAngular2serviceintoanAngular1factory.UsedowngradeInjectableandaddtheAngular1componentandtheconvertedservicetotheAngular1moduledefinition:

[app/ng1.module.ts]

import'angular';

import{ng1Article}from'./article.component';

import{ArticleService}from'./article.service';

import{downgradeInjectable}from'@angular/upgrade/static';

exportconstNg1AppModule=angular.module('Ng1AppModule',[])

.component('ng1Article',ng1Article)

.factory('ArticleService',downgradeInjectable(ArticleService));

That'sall!YoushouldbeabletoseetheAngular1componentrenderwiththedatapassedfromtheAngular2service.

Page 88: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoConnectingAngular1andAngular2withUpgradeModuleshowsyouhowtorunAngular1and2frameworkstogetherDowngradingAngular2componentstoAngular1directiveswithdowngradeComponentdemonstrateshowtouseanAngular2componentinsideanAngular1application

Page 89: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter2.ConqueringComponentsandDirectivesYourobjectiveistoiteratethroughthisanddisplaythearticletitleonlyifitissetasactive.Thischapterwillcoverthefollowingrecipes:

UsingdecoratorstobuildandstyleasimplecomponentPassingmembersfromaparentcomponenttoachildcomponentBindingtonativeelementattributesRegisteringhandlersonnativebrowsereventsGeneratingandcapturingcustomeventsusingEventEmitterAttachingbehaviortoDOMelementswithDirectivesProjectingnestedcontentusingngContentUsingngForandngIfstructuraldirectivesformodel-basedDOMcontrolReferencingelementsusingtemplatevariablesAttributepropertybindingUtilizingcomponentlifecyclehooksReferencingaparentcomponentfromachildcomponentConfiguringmutualparent-childawarenesswithViewChildandforwardRefConfiguringmutualparent-childawarenesswithContentChildandforwardRef

Page 90: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionDirectivesasyoucametoknowtheminAngular1havebeendoneawaywith.Intheirplace,wehavetwonewentities:componentsandthenewversionofdirectives.Angular2applicationsarenowcomponent-driven;withfurtherexposure,youwilldiscoverwhythisstyleissuperior.

Muchofthesyntaxisentirelynewandmayseemstrangeatfirst.Fearnot!TheunderpinningsoftheAngular2styleareelegantandmarvelousoncecompletelyunderstood.

Page 91: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UsingdecoratorstobuildandstyleasimplecomponentWhenwritinganapplicationcomponentinTypeScript,thereareseveralnewparadigmsthatyoumustbecomefamiliarandcomfortablewith.Thoughpossiblyintimidatinginitially,youwillfindthatyou'llbeabletocarryovermuchofyourcomprehensionofAngular1directives.

Note

Thecodeandaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6577/.

Page 92: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyOneofthesimplestimaginablecomponentswecanbuildisatemplateelementthatinterpolatessomevaluesintoitstemplate.InAngular1,onewaythiscouldbeachievedwasbycreatinganelementdirective,attachingsomedatatothescopeinsidethelinkfunction,andatemplatethatwouldreferencethedata.Iselectedthisdescriptionspecificallybecausenearlyallthoseconceptshavebeenbinned.

Supposeyouwanttocreateasimplearticlecomponentwithapseudotemplateasfollows:

<p>{{date}}</p>

<h1>{{title}}</h1>

<h3>Writtenby:{{author}}</h3>

YouwanttocreateacomponentthatwillliveinsideitsownHTMLtag,renderthetemplate,andinterpolatethevalues.

Page 93: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TheelementalbuildingblockofAngular2applicationsisthecomponent.Thiscomponentcouldgenerallybedefinedwithtwopieces:thecoreclassdefinitionandtheclassdecoration.

Writingtheclassdefinition

AllAngular2componentsbeginasaclass.Thisclassisusedtoinstantiatethecomponent,andanydatarequiredinsidethecomponenttemplatewillbeaccessiblefromtheclass'sproperties.Thus,thefoundationalclassforthecomponentwouldappearasfollows:

[app/article.component.ts]

exportclassArticleComponent{

currentDate:date;

title:string;

author:string;

constructor(){

this.currentDate=newDate();

this.title=`

FlightSecurityRestrictionstoInclude

Insults,MeanFaces

`;

this.author='Jake';

}

};

HereareafewthingstonoteforthosewhoarenewtoTypeScriptorES6ingeneral:

Youwillnotetheclassdefinitionisprefixedwithanexportkeyword.ThisisadherencetothenewES6moduleconvention,whichnaturallyisalsopartofTypeScript.AssumingtheArticleclassisdefinedinthefoo.tsfile,itcanbeimportedtoadifferentmoduleusingtheimportkeyword,andthepathtothatmodulewouldbeimport{Article}from'./foo';(thisassumesthattheimportingfileisinthesamedirectoryasfoo.ts).ThetitledefinitionusesthenewES6templatestringsyntax,apairofbackticks(``)insteadofthetraditionalsetofquotes('').Youwillfindyoubecomequitefondofthis,asitmeansthe''+''+''messinessformerlyusedtodefinemultilinetemplateswouldnolongerbenecessary.Allthepropertiesofthisclassaretyped.TypeScript'stypingsyntaxtakestheformofpropertyName:propertyType=optionalInitValue.JavaScriptis,ofcourse,alooselytypedlanguage,andJavaScriptiswhatthebrowserisinterpretinginthiscase.However,writingyourapplicationinTypeScriptallowsyoutoutilizetypesafetyatcompiletime,whichwillallowyoutoavoidundesirableandunanticipatedbehavior.AllES6classescomewithapredefinedconstructor()method,whichisinvokeduponinstantiation.Here,youareusingtheconstructortoinstantiatethepropertiesoftheclass,whichisaperfectlyfinestrategyformemberinitialization.Havingthememberproperty

Page 94: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

definitionoutsidetheconstructorisallowed,sinceitisusefulforaddingtypestoproperties;thus,hereyouaresimplyobviatingtheuseoftheconstructorsinceyouareabletoaddatypeandassignthevalueinthesameline.Amoresuccinctstylecouldbeasfollows:

[app/article.component.ts]

exportclassArticleComponent{

currentDate:date=newDate();

title:string=`

FlightSecurityRestrictionstoInclude

Insults,MeanFaces

`;

author:string='Jake';

}

TheTypeScriptcompilerwillautomaticallymovethememberinitializationprocessinsidetheclassconstructor,sothisversionandthepreviousonearebehaviorallyidentical.

Writingthecomponentclassdecorator

Althoughyouhavecreatedaclassthathasinformationassociatedwithit,itdoesnotyethaveanywaytointerfacewiththeDOM.Furthermore,thisclassisyettobeassignedwithanymeaninginthecontextofAngular2.Youcanaccomplishthiswithdecorators.

Note

DecoratorsareafeatureofTypeScriptbutnotbyanymeansuniquetothelanguage.Pythondevelopers,amongmanyothers,shouldbequitefamiliarwiththeconceptofclassmodulationviaexplicitdecoration.Generallyspeaking,itallowsyoutohavearegularizedmodificationofthedefinedclassesusingseparatelydefineddecorators.However,inAngular2,youwilllargelybeutilizingthedecoratorsprovidedtoyoubytheframeworktodeclarethevariousframeworkelements.Decoratorssuchas@ComponentaredefinedintheAngularsourcecodeasaComponentfunction,andthefunctionisappliedasadecoratorusingthe@symbol.

An@prefixsignalsthattheimportedfunctionshouldbeappliedasadecorator.Thesedecoratorsarevisuallyobviousbutwillusuallynotexistbythemselvesorwithanemptyobjectliteral.ThisisbecauseAngular2decoratorsaregenerallymadeusefulbytheirdecoratormetadata.ThisconceptcanbemademoreconcreteherebyusingthepredefinedComponentdecorator.

NothingisavailableforfreeinAngular2.Inordertousethecomponentdecorator,itmustbeimportedfromtheAngular2coremoduleintothemodulethatwishestouseit.YoucanthenprependthisComponenttotheclassdefinition:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({})

Page 95: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassArticleComponent{

currentDate:date=newDate();

title:string=`

FlightSecurityRestrictionstoInclude

Insults,MeanFaces

`;

author:string='Jake';

}

Asmentionedbefore,the@ComponentdecoratoracceptsaComponentMetadataobject,whichintheprecedingcodeisjustanemptyobjectliteral(notethattheprecedingcodewillnotcompile).Conceptually,thismetadataobjectisverysimilartothedirectivedefinitionobjectinAngular1.Here,youwanttoprovidethedecoratormetadataobjectwithtwoproperties,namelyselectorandtemplate:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<p>{{currentDate|date}}</p>

<h1>{{title}}</h1>

<h3>Writtenby:{{author}}</h3>

`

})

exportclassArticleComponent{

currentDate:date=newDate();

title:string=`

FlightSecurityRestrictionstoInclude

Insults,MeanFaces

`;

author:string='Jake';

}

NotethatselectoristhestringthatwillbeusedtofindwherethecomponentshouldbeinsertedintheDOM,andtemplateisobviouslythestringifiedHTMLtemplate.

Withallthis,youwillbeabletoseeyourarticlecomponentinactionwiththe<article></article>tag.

Page 96: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...TheclassdefinitionhassupplantedtheAngular1conceptofhavingacontroller.Thecomponentinstancesofthisclasshavememberpropertiesthatcanbeinterpolatedandboundintothetemplate,similarto$scopeinAngular1.

Inthetemplate,theinterpolationanddatabindingprocessesseemtooccurmuchinthesameway,theydidinAngular1.Thisisnotactuallythecase,whichisvisitedingreaterdetaillaterinthischapter.Thebuilt-indatemodifier,whichresemblesanAngular1filter,isnowdubbedwithapipealthoughitworksinaverysimilarfashion.

TheselectormetadatapropertyisastringrepresentingaCSSselector.Inthisdefinition,youtargetalltheoccurrencesofanarticletag,buttheselectorspecificityanddetailisofcourseabletohandleagreatdealofadditionalcomplexity.Usethistoyouradvantage.

Page 97: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...TheconceptthatmustbeinternalizedforAngular2neophytesisthetotalencapsulationofacomponent.ThisbookwillgointofurtherdetailaboutthedifferentabilitiesoftheComponentMetadataobject,buttheparadigmtheyandallclassdecoratorsintroduceistheconceptthatAngular2componentsareself-describing.Byexaminingtheclassdefinition,youcanwhollyreasonthedata,service,class,andinjectabledependencies.InAngular1,thiswasnotpossiblebecauseofthe"scopesoup"pattern.

OnecouldarguethatinAngular1,CSSstylingwasasecond-classcitizen.Thisisnolongerthecasewithcomponents,asthemetadataobjectoffersrobustsupportforcomplexstyling.Forexample,toitalicizetheauthorinyourArticlecomponent,usethiscode:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<p>{{currentDate|date}}</p>

<h1>{{title}}</h1>

<h3>Writtenby:{{author}}</h3>

`,

styles:[`

h3{

font-style:italic;

}

`]

})

exportclassArticleComponent{

currentDate:date=newDate();

title:string=`

FlightSecurityRestrictionstoInclude

Insults,MeanFaces

`;

author:string='Jake';

}

Angular2willusethisstylespropertyandcompileitintoageneratedstylesheet,whichitwillthenapplytoonlythiscomponent.YoudonothavetoworryabouttherestoftheHTMLh3tagsbeinginadvertentlystyled.ThisisbecauseAngular2'sgeneratedstylesheetwillensurethatonlythiscomponent—anditschildren—aresubjecttotheCSSruleslistedinthemetadataobject.

Note

Thisisintendedtoemulatethetotalmodularityofwebcomponents.However,sincewebcomponentsdonotyethaveuniversalsupport,Angular2'sdesignessentiallyperformsapolyfill

Page 98: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

forthisbehavior.

Page 99: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoPassingmembersfromaparentcomponentintoachildcomponentgoesthroughthebasicsofdownwarddataflowbetweencomponentsUsingngForandngIfstructuraldirectivesformodel-basedDOMcontrolinstructsyouonhowtoutilizesomeofAngular2'scorebuilt-indirectivesUtilizingcomponentlifecyclehooksgivesanexampleofhowyoucanintegratewithAngular2'scomponentrenderingflow

Page 100: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

PassingmembersfromaparentcomponentintoachildcomponentWiththedepartureoftheAngular1.xconceptof$scopeinheritance,mentally(partiallyorentirely)remodelinghowinformationwouldbepassedaroundyourapplicationisamust.Initsplace,youhaveanentirelynewsystemofpropagatinginformationthroughouttheapplication'shierarchy.

Gonealsoistheconceptofdefaultingtobidirectionaldatabinding.Althoughitmadeforanapplicationthatwassimplertoreasonabout,bidirectionaldatabindingincursanunforgivablyexpensivedragonperformance.Thisnewsystemoperatesinanasymmetricfashion:membersarepropagateddownwardsthroughthecomponenttree,butnotupwardsunlessexplicitlyperformed.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6543/.

Page 101: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadasimpleapplicationthatintendedto(butcurrentlycannot)passdatafromaparentArticleComponenttoachildAttributionComponent:

[app/components.ts]

import{Component}from'@angular/core';

@Component({

selector:'attribution',

template:`

<h3>Writtenby:{{author}}</h3>

`

})

exportclassAttributionComponent{

author:string;

}

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<attribution></attribution>

`

})

exportclassArticleComponent{

title:string='BelchingChoirBeginsWorldTour';

name:string='Jake';

}

Page 102: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Inthisinitialimplementation,thecomponentsdefinedherearenotyetawareofeachother,andthe<attribution></attribution>tagwillremaininertintheDOM.Thisisagoodthing!Itmeansthesetwocomponentsarecompletelydecoupled,andyouareabletoonlyintroduceconnectionlogicasnecessary.

Connectingthecomponents

First,sincethe<attribution></attribution>tagappearsinsidetheArticlecomponent,youmustmakethecomponentawareoftheexistenceofAttributionComponent.ThisisaccomplishedbyintroducingthecomponentinthemoduleinwhichArticleComponentisalsodeclared:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{ArticleComponent,AttributionComponent}

from'./components';

@NgModule({

imports:[

BrowserModule

],

declarations:[

ArticleComponent,

AttributionComponent

],

bootstrap:[

ArticleComponent

]

})

exportclassAppModule{}

Note

Forthepurposeofthisrecipe,don'tconcernyourselfjustyetwiththedetailsofwhatNgModuleisdoing.Intheexampleinthisrecipe,theentireapplicationisjustaninstanceofArticleComponentwithAttributionComponentinsideit.So,allthecomponentdeclarationscanbedoneinsidethesamemodule.

Withthis,youwillseethatArticleComponentisabletomatchthe<attribution></attribution>tagwiththeAttributionComponentdefinition.

Note

Insideasinglemodule,theorderofthedefinitioncouldmatteralot.ES6andTypeScriptclass

Page 103: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

declarationsarenothoisted,soyoucannotreferencethematallbeforethedeclarationwithoutgeneratingerrors.Inthisrecipe,sinceArticleComponentisdefinedbeforeAttributionComponent,theformercannotdirectlyreferencethelatterinsideitsdefinition.

IfyouweretoinsteaddefineAttributionComponentinsideaseparatemoduleandimportitwiththemoduleloader,theorderissuebecomesirrelevant.Asyouwillnotice,thisisoneoftheexcellentbenefitsofhavingahighlymodularapplicationstructure.

OnecaveattothisisthatAngulardoesmakeitpossibletodoout-of-orderclassreferencesusingaforwardRef.However,ifsolvingtheorderproblemispossiblebysplittingitintoseparatemodules,thatispreferredoverforwardRef.

Thisbeingthecase,goaheadandsplityourcomponentfileintotwoseparatemodulesandimportthemaccordingly:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{ArticleComponent}from'./article.component';

import{AttributionComponent}from'./attribution.component';

@NgModule({

imports:[

BrowserModule

],

declarations:[

ArticleComponent,

AttributionComponent

],

bootstrap:[

ArticleComponent

]

})

exportclassAppModule{}

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<attribution></attribution>

`

})

exportclassArticleComponent{

title:string='BelchingChoirBeginsWorldTour';

name:string='Jake';

Page 104: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

[app/attribution.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'attribution',

template:`

<h3>Writtenby:{{author}}</h3>

`

})

exportclassAttributionComponent{

author:string;

}

Declaringinputs

SimilartotheAngular1directive'sscopepropertyobject,inAngular2,youmustdeclarethemembersoftheparentcomponenttobringthemdowntothechildcomponent.InAngular1,thiscouldbedoneimplicitlywithaninherited$scope,butthisisnolongerthecase.Angular2componentinputsmustbeexplicitlydefined.

Tip

AnotherimportantdifferencebetweenAngular1andAngular2isthat@InputinAngular2isaunidirectionaldatabindingfeature.Dataupdateswillflowdownwards,andtheparentwillnotbeupdatedunlessexplicitlynotified.

TheprocessofdeclaringinputsinachildcomponentisdonethroughtheInputdecorator,butthedecoratorisinvokedinsidetheclassdefinitioninsteadofdoingsoinfrontofit.Inputisimportedfromthecoremoduleandinvokedinsidetheclassdefinitionthatispairedwithamember.

Note

Don'tletthisconfuseyou.Theimplementationoftheactualdecoratingfunctionishiddenfromyousinceitisimportedasasingletarget,sodon'tthinkmuchaboutwhatthe@Input()syntaxisdoing.ThereisadefinedInputfunctionintheAngularsource,andyouarecertainlyinvokingthismethodhere.However,foryourpurposes,itismerelydeclaringthememberthatfollowsitastheonethatwillbepassedinexplicitlyfromtheparentcomponent.YouuseitinthesamewayastheComponentdecorator,justinadifferentplace.

[app/attribution.component.ts]

import{Component,Input}from'@angular/core';

@Component({

selector:'attribution',

Page 105: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

template:`

<h3>Writtenby:{{author}}</h3>

`

})

exportclassAttributionComponent{

@Input()author:string;

}

Next,youmustpassthevalueboundtothechildcomponenttagtotheparentcomponent.Inthecontextofthisrecipe,youwanttopassthenamepropertyoftheArticlecomponentobjecttotheauthorpropertyoftheAttributioncomponentobject.Onewayofaccomplishingthisisbyusingthesquarebracketnotationonthetagattribute,whichspecifiestheattributestringasbounddata:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<attribution[author]="name"></attribution>

`

})

exportclassArticleComponent{

title:string='BelchingChoirBeginsWorldTour';

name:string='Jake';

}

Withthis,youhavesuccessfullypassedamemberpropertydown,fromaparenttoachildcomponent!

Page 106: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Recallthatthestartingpointofthisexamplewasthatwehadtwocomponentsthatdidn'tknowtheotherexists,eventhoughtheyaredefinedandexportedinsidethesamemodule.Theprocessdemonstratedinthisrecipeistoprovidethechildcomponenttotheparentcomponent,configurethechildcomponenttoexpectthatamemberwillbeboundtoaninputattribute,andfinallyprovidethatmemberinthetemplateoftheparentcomponent.

Page 107: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Somepeoplehaveanissuewiththesquarebracketnotation.ItisvalidHTML,butsomedevelopersfeelitisunintuitiveandlooksodd.

Tip

Additionally,thebracketnotationisnotvalidXML.DevelopersusingHTMLgeneratedthroughXSLTwillnotbeabletoutilizethenewsyntax.Fortunately,everywherethenewAngular2syntaxutilizesnewnew[]or()syntax,thereisanequivalentsyntaxthattheframeworksupportswhichwillbehaveidentically.

Insteadofusingpairsofsquarebrackets,youcanprefixtheattributenamewithbind-anditwillbehaveidentically:

[app/article.component.ts]

import{Component,Input}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<attributionbind-author="name"></attribution>

`

})

exportclassArticleComponent{

title:string='BelchingChoirBeginsWorldTour';

name:string='Jake';

}

Angularexpressions

Notethatthevalueoftheattributenameisnotastringbutanexpression.Angularknowshowtoevaluatethisexpressioninthecontextoftheparentcomponent.AsisthecasewithAngularexpressionsthough,youaremorethanwelcometoprovideastaticvalueandAngularwillhappilyevaluateitandprovideittothechildcomponent.

Forexample,thefollowingchangewouldhardcodethechildcomponenttoassignthestring"MikeSnifferpippets"astheauthorproperty:

[app/article.component.ts]

import{Component,Input}from'@angular/core';

import{AttributionComponent}from'./attribution.component';

@Component({

selector:'article',

template:`

Page 108: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

<h1>{{title}}</h1>

<attribution[author]="'MikeSnifferpippets'"></attribution>

`,

directives:[AttributionComponent]

})

exportclassArticleComponent{

title:string='BelchingChoirBeginsWorldTour';

name:string='Jake';

}

Unidirectionaldatabinding

Thedatabindingyouhavesetupinthisrecipeisactuallyunidirectional.Morespecifically,changesintheparentcomponentmemberwillpropagatedownwardstothechildcomponent,butchangestothechildcomponentmemberwillnotpropagateupwards.Thiswillbeexploredfurtherinanotherrecipe,butitisimportanttokeepinmindthattheAngular2dataflowis,bydefault,downwardsthroughthecomponenttree.

Membermethods

Angulardoesn'tcareaboutthenatureoftheboundvalue.TypeScriptwillenforcetypecorrectnessshouldyoudeviatefromthedeclaredtype,butyouarewelcometopassparentmethodstothechildwiththisstrategyaswell.

Tip

Keepinmindthatpassingamethodboundinthiswaydoesnotenforcethecontextinwhichitisevaluated.Iftheparentcomponentpassesamembermethodthatutilizesthethiskeywordandthechildcomponentevaluatesit,thiswillrefertothechildcomponentinstanceandnottheparentcomponent.Therefore,ifthemethodtriestoaccessthememberdataontheparentcomponent,itwillnotbeavailable.

Thereareanumberofwaystomitigatethisproblem.Generallythough,ifyoufindyouarepassingaparentmembermethoddowntothechildcomponentandinvokingit,thereisprobablyabetterwaytodesignyourapplication.

Page 109: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUsingdecoratorstobuildandstyleasimplecomponentdescribesthebuildingblocksofimplementinganAngular2componentBindingtonativeelementattributesshowshowAngular2interfaceswithHTMLelementattributesRegisteringhandlersonnativebrowsereventsdemonstrateshowyoucaneasilyattachbehaviortobrowserevents.GeneratingandcapturingcustomeventsusingEventEmitterdetailshowtopropagateinformationupwardsbetweencomponents.UsingngForandngIfstructuraldirectivesformodel-basedDOMcontrolinstructsyouonhowtoutilizesomeofAngular2'scorebuilt-indirectives.

Page 110: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BindingtonativeelementattributesInAngular1,itwasexpectedthatthedeveloperwouldutilizethebuilt-inreplacementdirectivesforelementattributesthathadmeaningfulDOMbehaviorattachedtothem.ThiswasduetothefactthatmanyoftheseattributeshadbehaviorthatwasincompatiblewithhowAngular1databindingoperated.InAngular2,thesespecialattributedirectivesaredoneawaywith,andthebindingbehaviorandsyntaxissubsumedintothenormalbindingbehavior.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/8313/.

Page 111: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Bindingtothenativeattributeisassimpleasplacingsquarebracketsaroundtheattributeandtreatingitasnormalbounddata:

[app/logo.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'logo',

template:'<img[src]="logoUrl">'

})

exportclassLogoComponent{

logoUrl:string=

'//angular.io/resources/images/logos/standard/logo-nav.png';

}

Withthissetup,the<img>elementwilldutifullyfetchandshowtheimagewhenitisprovidedbyAngular.

Page 112: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Thisisadifferentsolutiontothesameproblemthatng-srcsolvedinAngular1.Thebrowserislookingforansrcattributeonthetag.Sincethesquarebracketsareincludedaspartoftheattributestring,thebrowserwillnotfindoneandthereforenotmakearequest.[src]willonlymakeanimagerequestoncethevalueisfilledandprovidedtotheelement.

Page 113: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoPassingmembersfromaparentcomponentintoachildcomponentgoesthroughthebasicsofdownwarddataflowbetweencomponents.Registeringhandlersonnativebrowsereventsdemonstrateshowyoucaneasilyattachbehaviortobrowserevents.AttachingbehaviortoDOMelementswithdirectivesdemonstrateshowtoattachbehaviortoelementswithattributedirectives.ReferencingelementsusingtemplatevariablesdemonstratesAngular2'snewtemplatevariableconstruct.AttributepropertybindingshowsAngular2'scleverwayofdeepreferencingelementproperties.

Page 114: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

RegisteringhandlersonnativebrowsereventsInAngular2,theotherhemisphereofbindingthatisneededforafullyfunctioningapplicationiseventbinding.TheAngular2eventbindingsyntaxissimilartothatofdatabinding.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4437/.

Page 115: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouwantedtocreateanarticleapplicationthatcountedshares,andyoubeganwiththefollowingskeleton:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{shareCt}}</p>

<button>Share</button>

`

})

exportclassArticleComponent{

title:string='PoliceApprehendTiramisuThieves';

shareCt:number=0;

}

Page 116: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TheAngular2eventbindingsyntaxisaccomplishedwithapairofparenthesessurroundingtheeventtype.Inthiscase,eventsthatyouwishtolistenforwillhaveatypepropertyofclick,andthisiswhattheywillbeboundagainst.Thevalueoftheboundeventattributeisanexpression,soyoucaninvokethemethodasahandlerwithinit:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{shareCt}}</p>

<button(click)="share()">Share</button>

`

})

exportclassArticleComponent{

title:string='PoliceApprehendTiramisuThieves';

shareCt:number=0;

share():void{

++this.shareCt;

}

}

Page 117: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Angularwatchesfortheeventbindingsyntax(click)andaddsaclicklistenertoArticleComponent,boundtotheshare()handler.Whenthiseventisobserved,itevaluatestheexpressionattachedtotheevent,whichinthiscasewillinvokeamethoddefinedonthecomponent.

Page 118: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Sincecapturingtheeventmustoccurinanexpression,youareprovidedwithan$eventparameterintheexpression,whichwillusuallybepassedasanargumenttothehandlermethod.ThisissimilartotheprocessinAngular1.Inspectingthis$eventobjectrevealsitasthevanillaclickeventgeneratedbythebrowser:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{shareCt}}</p>

<button(click)="share($event)">Share</button>

`

})

exportclassArticleComponent{

title:string='PoliceApprehendTiramisuThieves';

shareCt:number=0;

share(e:Event):void{

console.log(e);//MouseEvent

++this.shareCt;

}

}

Note

Youwillalsonotethattheshare()methodhereisdemonstratinghowtypingcanbeappliedtotheparametersandthereturnvalueofthemethod:

myMethod(arg1:arg1type,arg2:arg2type,...):returnType

Aswithmemberbinding,youarealsoabletouseanalternateeventbindingsyntaxifyoudonotcaretouseasetofparentheses.Prefixingon-totheeventattributewillprovideyouwithidenticalbehavior:

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{shareCt}}</p>

<buttonon-click="share($event)">Share</button>

`

})

exportclassArticle{

Page 119: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

title:string='PoliceApprehendTiramisuThieves';

shareCt:number=0;

share(e:Event):void{

++this.shareCt;

}

}

Page 120: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBindingtonativeelementattributesshowshowAngular2interfaceswithHTMLelementattributes.GeneratingandcapturingcustomeventsusingEventEmitterdetailshowtopropagateinformationupwardsbetweencomponents.AttachingbehaviortoDOMelementswithdirectivesdemonstrateshowtoattachbehaviortoelementswithattributedirectives.AttributepropertybindingshowsAngular2'scleverwayofdeepreferencingelementproperties.

Page 121: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GeneratingandcapturingcustomeventsusingEventEmitterInthewakeofthedisappearanceof$scope,Angularwasleftwithavoidforpropagatinginformationupthecomponenttree.Thisvoidisfilledinpartbycustomevents,andtheyrepresenttheYintothedownwarddatabindingYang.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/8611/.

Page 122: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadanArticleapplicationasfollows:

[app/text-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'text-editor',

template:`

<textarea></textarea>

`

})

exportclassTextEditorComponent{}

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Wordcount:{{wordCount}}</p>

<text-editor></text-editor>

`

})

exportclassArticleComponent{

title:string=`

MaternityWardResortstoRockPaperScissorsFollowing

BabyMixup`;

wordCount:number=0;

updateWordCount(e:number):void{

this.wordCount=e;

}

}

Thisapplicationwillideallybeabletoreadthecontentoftextareawhenthereisachange,andalsocountthenumberofwordsandreportittotheparentcomponenttobeinterpolated.Asisthecase,noneofthisisimplemented.

Page 123: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...AdeveloperthinkingintermsofAngular1wouldattachng-modeltotextarea,use$scope.$watchonthemodeldata,andpassthedatatotheparentvia$scopeorsomeothermeans.Unfortunatelyforsuchadeveloper,theseconstructsareradicallydifferentornon-existentinAngular2.Fearnot!Thenewimplementationismoreexpressive,moremodular,andmuchcleaner.

Capturingtheeventdata

ngModelstillexistsinAngular2,anditwouldcertainlybesuitablehere.However,youdon'tactuallyneedtousengModelatall,andinthiscase,itallowsyoutobemoreexplicitaboutwhenyourapplicationtakesaction.First,youmustretrievethetextfromthetextareaelementandmakeitusableinTextEditorComponent:

[app/text-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'text-editor',

template:`

<textarea(keyup)="emitWordCount($event)"></textarea>

`

})

exportclassTextEditorComponent{

emitWordCount(e:Event):void{

console.log(

(e.target.value.match(/\S+/g)||[]).length);

}

}

Excellent!Asclaimed,youdon'tneedtousengModeltoacquiretheelement'scontents.What'smore,youarenowabletoutilizenativebrowsereventstoexplicitlydefinewhenyouwantTextEditorComponenttotakeaction.

Withthis,youaresettingalisteneronthenativebrowser'skeyupevent,firedfromthetextareaelement.Thiseventhasatargetpropertythatexposesthevalueofthetextintheelement,whichisexactlywhatyouwanttouse.Thecomponentthenusesasimpleregularexpressiontocountthenumberofnon-whitespacesequences.Thisisyourwordcount.

Emittingacustomevent

console.logdoesnothelptoinformtheparentcomponentofthewordcountyouarecalculating.Todothis,youneedtocreateacustomeventandemititupwards:

[app/text-editor.component.ts]

Page 124: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Component,EventEmitter,Output}from'@angular/core';

@Component({

selector:'text-editor',

template:`

<textarea(keyup)="emitWordCount($event)"></textarea>

`

})

exportclassTextEditorComponent{

@Output()countUpdate=newEventEmitter<number>();

emitWordCount(e:Event):void{

this.countUpdate.emit(

(e.target.value.match(/\S+/g)||[]).length);

}

}

Usingthe@OutputdecoratorallowsyoutoinstantiateanEventEmittermemberonthechildcomponentthattheparentcomponentwillbeabletolistento.ThisEventEmittermember,likeanyotherclassmember,isavailableasthis.countUpdate.Thechildcomponentisabletosendeventsupwardbyinvokingtheemit()methodonthismember,andtheargumenttothismethodisthevaluewhichyouwishtosendtotheevent.Here,sinceyouwanttosendanintegercountofwords,youinstantiatetheEventEmittermemberbytypingitasa<number>emitter.

Listeningforcustomevents

Sofar,youarethroughwithonlyhalftheimplementation,asthesecustomeventsarebeingfiredoffintotheetherofthebrowserwithnolisteners.Sincethemethodyouneedtouseisalreadydefinedontheparentcomponent,allyouneedtodoishookintotheeventlistenertothatmethod.

The()templatesyntaxisusedtoaddlistenerstoevents,andAngulardoesnotdiscriminatebetweennativebrowsereventsandeventsthatoriginatefromEventEmitters.Thus,sinceyoudeclaredthechildcomponent'sEventEmitteras@Output,youwillbeabletoaddalistenerforeventsthatcomefromitontheparentcomponent,asfollows:

[app/article.component.ts]

import{Component}from'angular2/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Wordcount:{{wordCount}}</p>

<text-editor(countUpdate)="updateWordCount($event)">

</text-editor>

`

})

exportclassArticleComponent{

Page 125: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

title:string=`

MaternityWardResortstoRockPaperScissorsFollowing

BabyMixup`;

wordCount:number=0;

updateWordCount(e:number):void{

this.wordCount=e;

}

}

Withthis,yourapplicationshouldcorrectlycountthewordsintheTextEditorcomponentandupdatethevalueintheArticlecomponent.

Page 126: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Using@OutputinconjunctionwithEventEmitterallowsyoutocreatechildcomponentsthatexposeanAPIfortheparentcomponenttohookinto.TheEventEmittersendstheeventsupwardwithitsemitmethod,andtheparentcomponentcansubscribetothembybindingtotheemitteroutput.

Theflowofthisexampleisasfollows:

1. Thekeystrokeinsidetextareacausesthenativebrowser'skeyupevent.2. TheTextEditorcomponenthasalistenersetonthisevent,sotheattachedexpressionis

evaluated,whichwillinvokeemitWordCount.3. TheemitWordCountinspectstheEventobjectandextractsthetextfromtheassociated

DOMelement.ItparsesthetextforthenumberofcontainedwordsandinvokestheEventEmitter.emitmethod.

4. TheEventEmittermethodemitsaneventassociatedwiththedeclaredcountUpdate@Outputmember.

5. TheArticleComponentseesthiseventandinvokestheattachedexpression.TheexpressioninvokesupdateWordCount,passingintheeventvalue.

6. TheArticleComponentpropertyisupdated,andsincethisvalueisinterpolatedintheview,Angularhonorsthedatabindingprocessbyupdatingtheview.

Page 127: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...ThenameEventEmitterisabitdeceiving.Ifyou'repayingattention,youwillnoticethattheparentcomponentmembermethodinvokedinthehandlerdoesnothaveatypedparameter.Youwillalsonoticethatyouaredirectlyassigningthatparametertothemembertypedasnumber.Thisshouldseemoddasthetemplateexpressioninvokingthemethodispassing$event,whichyouusedearlierasabrowserEventobject.Thisseemslikeamismatchbecauseitisamismatch.Ifyoubindtonativebrowserevents,theeventyouwillobservecanonlybethenativebrowsereventobject.Ifyoubindtocustomevents,theeventyouwillobserveiswhateverwaspassedwhenemitwasinvoked.Here,theparametertoupdateWordCount()issimplytheintegeryouprovidedwiththis.countUpdate.emit().

Alsonotethatyouarenotrequiredtoprovideavaluefortheemittedevent.YoucanstilluseEventEmittertosignaltoaparentcomponentthataneventhasoccurredandthatitshouldevaluatetheboundexpression.Todothis,yousimplycreateanuntypedemitterwithnewEventEmitter()andinvokeemit()withnoarguments.$eventshouldbeundefined.

Itisnotpossibletopassmultiplevaluesascustomevents.Tosendmultiplepiecesofdata,youneedtocombinethemintoanobjectorarray.

Page 128: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBindingtonativeelementattributesshowshowAngular2interfaceswithHTMLelementattributes.Registeringhandlersonnativebrowsereventsdemonstrateshowyoucaneasilyattachbehaviortobrowserevents.

Page 129: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

AttachingbehaviortoDOMelementswithdirectivesInthecourseofcreatingapplications,youwilloftenfinditusefultobeabletoattachcomponent-stylebehaviortoDOMelements,butwithouttheneedtohavetemplating.IfyouweretoattempttoconstructanAngular2componentwithoutprovidingatemplateinsomeway,youwillmeetwithasternerrortellingyouthatsomeformoftemplateisrequired.

HereliesthedifferencebetweenAngular2componentsanddirectives:componentshaveviews(whichcantaketheformofatemplate,templateUrl,or@Viewdecorator),whereasdirectivesdonot.Theyotherwisebehaveidenticallyandprovideyouwiththesamebehavior.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/3292/.

Page 130: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhavethefollowingapplication:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`<h1>{{title}}</h1>`,

styles:[`

h1{

text-overflow:ellipsis;

white-space:nowrap;

overflow:hidden;

max-width:300px;

}

`]

})

exportclassArticleComponent{

title:string=`PresidentialCandidatesRespondto

AllegationsInvolvingAbilitytoDunk`;

}

Currently,thisapplicationisusingCSStotruncatethearticletitlewithanellipsis.YouwouldliketoexpandthisapplicationsothattheArticlecomponentrevealstheentiretitlewhenclickedbysimplyaddinganHTMLattribute.

Page 131: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbydefiningthebasicclassthatwillpowertheattributedirectiveandaddittotheapplicationmodule:

[app/click-to-reveal.directive.ts]

exportclassClickToRevealDirective{

reveal(target){

target.style['white-space']='normal';

}

}

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{ArticleComponent}from'./article.component';

import{ClickToRevealDirective}

from'./click-to-reveal.directive';

@NgModule({

imports:[

BrowserModule

],

declarations:[

ArticleComponent,

ClickToRevealDirective

],

bootstrap:[

ArticleComponent

]

})

exportclassAppModule{}

First,youmustdecoratetheClickToRevealDirectiveclassas@DirectiveanduseitinsidetheArticlecomponent:

[app/click-to-reveal.directive.ts]

import{Directive}from'@angular/core';

@Directive({

selector:'[click-to-reveal]'

})

exportclassClickToRevealDirective{

reveal(target){

target.style['white-space']='normal';

}

}

Page 132: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Next,addtheattributetotheelementthatyouwishtoapplythedirectiveto:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`<h1click-to-reveal>{{title}}</h1>`,

styles:[`

h1{

text-overflow:ellipsis;

white-space:nowrap;

overflow:hidden;

max-width:300px;

}

`]

})

exportclassArticleComponent{

title:string;

constructor(){

this.title=`PresidentialCandidatesRespondto

AllegationsInvolvingAbilitytoDunk`;

}

}

Note

NotethattheDirectiveisusinganattributeCSSselectortoassociateitselfwithanyelementsthathaveclick-to-reveal.ThisofcourseapproximatesanAngular1attribute'sdirectivebehavior,butthisformisfarmoreflexiblesinceitcanwieldtheinnatematchabilityofselectors.

NowthattheArticlecomponentisawareofClickToRevealDirective,youmustprovideittheabilitytoattachitselftoclickevents.

AttachingtoeventswithHostListeners

Anattentivedeveloperwillhavenoticedthatupuntilthispointinthechapter,youhavecreatedcomponentsthatlistentotheeventsgeneratedbythechildren.Thisisnoproblemsinceyoucanexpressivelysetlistenersinaparentcomponenttemplateonthechildtag.

However,inthissituation,youarelookingtoaddalistenertothesameelementthatthedirectiveisbeingattachedto.What'smore,youdonothaveagoodwayofaddinganeventbindingexpressiontothetemplatefrominsideadirective.Ideally,youwouldliketonothavetoexposethismethodfrominsidethedirective.Howshouldyouproceedthen?

ThesolutionistoutilizeanewAngularconstructcalledHostListener.Simplyput,itallowsyoutocaptureself-originatingeventsandhandletheminternally:

Page 133: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/click-to-reveal.directive.ts]

import{Directive,HostListener}from'@angular/core';

@Directive({

selector:'[click-to-reveal]'

})

exportclassClickToRevealDirective{

@HostListener('click',['$event.target'])

reveal(target){

target.style['white-space']='normal';

}

}

Withthis,clickeventsonthe<h1>elementshouldsuccessfullyinvokethereveal()method.

Page 134: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Thedirectiveneedsawaytoattachtonativeclickevents.Furthermore,itneedsawaytocaptureobjectssuchas$eventthatAngularprovidestoyou;theseobjectswouldnormallybecapturedinthebindingexpression.

@HostListenerdecoratesadirectivemethodtoactasthedesignatedeventhandler.Thefirstargumentinitsinvocationistheeventidentificationstring(here,click,butitcouldjustaseasilybeacustomeventfromEventEmitter),andthesecondargumentisanarrayofstringargumentsthatareevaluatedasexpressions.

Page 135: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...YouarenotrestrictedtooneHostListenerinsideadirective.Usingitmerelyassociatesaneventwithadirectivemethod.SoyouareabletostackmultipleHostListenerdeclarationsonasinglehandler,forexample,tolistenforbothaclickandmouseoverevent.

Page 136: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUsingdecoratorstobuildandstyleasimplecomponentdescribesthebuildingblocksofimplementinganAngular2componentPassingmembersfromaparentcomponentintoachildcomponentgoesthroughthebasicsofdownwarddataflowbetweencomponentsUsingngForandngIfstructuraldirectivesformodel-basedDOMcontrolinstructsyouinhowtoutilizesomeofAngular2'scorebuilt-indirectivesAttributepropertybindingshowsAngular2'scleverwayofdeepreferencingelementproperties

Page 137: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ProjectingnestedcontentusingngContentUtilizingcomponentsasstandalonetagsthatareself-containedandwhollymanagetheircontentsisacleanpattern,butyouwillfrequentlyfindthatyourcomponenttagsdemandthattheyenclosecontent.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6172/.

Page 138: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingapplication:

[app/ad-section.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'ad-section',

template:`

<ahref="#">{{adText}}</a>

`

})

exportclassAdSectionComponent{

adText:string='Selfiesticks40%off!';

}

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>U.S.senatorsareupinarmsfollowingtherecentruling

strippingthemoftheirbelovedselfiesticks.</p>

<p>Abipartisancommitteedraftedaresolutiontosmuggle

selfiesticksontothefloorbyanymeansnecessary.</p>

`

})

exportclassArticleComponent{

title:string='SelfieSticksBannedfromSenateFloor';

}

YourobjectivehereistomodifythissothattheAdSectioncomponentcanbeincorporatedintotheArticlecomponentwithoutinterferingwithitscontent.

Page 139: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TheAdSectioncomponentwantstoincorporateanextraelementaroundtheexistingArticlecontent.Thisiseasytoaccomplish:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<ad-section>

<p>U.S.senatorsareupinarmsfollowingtherecentruling

strippingthemoftheirbelovedselfiesticks.</p>

<p>Abipartisancommitteedraftedaresolutiontosmuggle

selfiesticksontothefloorbyanymeansnecessary.</p>

</ad-section>

`

})

exportclassArticleComponent{

title:string='SelfieSticksBannedfromSenateFloor';

}

Youwillnoticethoughthatthisisadestructiveoperation.WhenrenderingAdSectionComponent,Angularisnotconcernedaboutanycontentthatisinsideit.ItseesthatAdSectionComponenthasatemplateassociatedwithit,anditdutifullysupplantstheelement'scontentswithit;[email protected],thatwipesoutthe<p>tagsthatyouwanttoretain.

Topreservethem,youmustinstructAngularhowitshouldmanagewrappedcontent.Thiscanbeaccomplishedwithan<ng-content>tag:

[app/ad-section.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'ad-section',

template:`

<ahref="#">{{adText}}</a>

<ng-contentselect="p"></ng-content>

`

})

exportclassAdSectionComponent{

adText:string='Selfiesticks40%off!';

}

Withthis,theadanchorelementisinsertedbeforethewrappedcontent.

Page 140: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Similartohowng-transcludeworkedinAngular1,ng-contentservestointerpolatethecomponenttag'swrappedcontentintoitstemplate.Thedifferencehereisthatng-contentusesaselectattributetotargetthewrappedelements.ThisissimplyaCSSselector,operatinginthesamewayinwhich@ComponentdecoratorshandletheselectorpropertyinComponentMetadata.

Page 141: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Theselectattributeinthisexamplewassuperfluous,asitendedupselectingtheentiretyofthewrappedcontent.Ofcourse,iftheselectvalueonlymatchedsomeofthewrappedcontent,itwouldteaseoutonlythoseelementsandinterpolatethem.<ng-content>willbydefaultinserttheentiretyofthewrappedcontentifyoudeclinetoprovideitwithaselectvalue.

AlsonotethattheselectattributeisalimitedCSSselector.Itisnotcapableofperformingcomplexselectionssuchas:nth-child,anditisonlyabletotargettop-levelelementsinsidethewrappingtags.Forexample,inthisapplication,theparagraphtaginside<div><p>Blah</p></div>wouldnotbeincludedwithaselect="p"attributevalue.

Page 142: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoReferencingaparentcomponentfromachildcomponentdescribeshowacomponentcangainadirectreferencetoitsparentviainjectionConfiguringmutualparent-childawarenesswithViewChildandforwardRefinstructsyouonhowtoproperlyuseViewChildtoreferencechildcomponentobjectinstancesConfiguringMutualParent-ChildAwarenesswithContentChildandforwardRefinstructsyouonhowtoproperlyuseContentChildtoreferencechildcomponentobjectinstances

Page 143: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UsingngForandngIfstructuraldirectivesformodel-basedDOMcontrolAnydeveloperthathasusedaclientframeworkisintimatelyfamiliarwithtwobasicoperationsinanapplication:iterativerenderingfromacollectionandconditionalrendering.ThenewAngular2implementationslookabitdifferentbutoperateinmuchthesameway.

Note

Thecode,links,andaliveexampleareavailableathttp://ngcookbook.herokuapp.com/3211/.

Page 144: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingapplication:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-list',

template:''

})

exportclassArticleListComponent{

articles:Array<Object>=[

{title:'Foo',active:true},

{title:'Bar',active:false},

{title:'Baz',active:true}

];

}

Yourobjectiveistoiteratethroughthisanddisplaythearticletitleonlyifitissetasactive.

Page 145: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...SimilartoAngular1,Angular2providesyouwithdirectivestoaccomplishthistask.ngForisusedtoiteratethroughthearticlescollection:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-list',

template:`

<div*ngFor="letarticleofarticles;leti=index">

<h1>

{{i}}:{{article.title}}

</h1>

</div>

`

})

exportclassArticleListComponent{

articles:Array<Object>=[

{title:'Foo',active:true},

{title:'Bar',active:false},

{title:'Baz',active:true}

];

}

SimilartongFor,ngIfcanbeincorporatedasfollows:

[app/article-list.component.ts]

import{Component}from'angular2/core';

@Component({

selector:'article-list',

template:`

<div*ngFor="letarticleofarticles;leti=index">

<h1*ngIf="article.active">

{{i}}:{{article.title}}

</h1>

</div>

`

})

exportclassArticleListComponent{

articles:Array<Object>=[

{title:'Foo',active:true},

{title:'Bar',active:false},

{title:'Baz',active:true}

];

}

Page 146: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Withthis,youwillseethatonlytheobjectsinthearticlesarraywithactive:truearerendered.

Page 147: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Atfirst,theasteriskandpoundsignnotationcouldbeconfusingformanydevelopers.Formostapplications,youwillnotneedtoknowhowthissyntacticsugaractuallyworksbehindthescenes.

Inreality,Angulardecomposesallthestructuraldirectivesprefixedwith*toutilizeatemplate.First,AngularbreaksdownngForandngIftousethetemplatedirectivesonthesameelement.Thesyntaxdoesnotchangemuchyet:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-list',

template:`

<divtemplate="ngForletarticleofarticles;leti=index">

<h1template="ngIfarticle.active">

{{i}}:{{article.title}}

</h1>

</div>

`

})

exportclassArticleList{

articles:Array<Object>,

constructor(){

this.articles=[

{title:'Foo',active:true},

{title:'Bar',active:false},

{title:'Baz',active:true}

];

}

}

Followingthis,Angulardecomposesthistemplatedirectiveintoawrapping<template>element:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-list',

template:`

<templatengForlet-article[ngForOf]="articles"let-i="index">

<div>

<template[ngIf]="article.active">

<h1>

{{i}}:{{article.title}}

Page 148: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

</h1>

</template>

</div>

</template>

`

})

exportclassArticleList{

articles:Array<Object>,

constructor(){

this.articles=[

{title:'Foo',active:true},

{title:'Bar',active:false},

{title:'Baz',active:true}

];

}

}

Note

Notethatboththeversionsdisplayedhere—eitherusingthetemplatedirectiveorthe<template>element—willbehaveidenticallytousingtheoriginalstructuraldirectives.Thatbeingsaid,theregenerallywillnotbeareasontoeverdoitthisway;thisismerelyademonstrationtoshowyouhowAngularunderstandsthesedirectivesbehindthescenes.

Tip

WheninspectingtheactualDOMoftheseexamplesusingngForandngIf,youwillbeabletoseeAngular'sautomaticallyaddedHTMLcommentsthatdescribehowitinterpretsyourmarkupandtranslatesitintotemplatebindings.

Page 149: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...ThetemplateelementisbornoutoftheWebComponents'specification.TemplatesareadefinitionofhowaDOMsubtreecaneventuallybedefinedasaunit,buttheelementsthatappearwithinitarenotcreatedoractiveuntilthetemplateisactuallyusedtocreateaninstancefromthattemplate.Notallwebbrowserssupportwebcomponents,soAngular2doesapolyfilltoemulatepropertemplatebehavior.

Inthisway,thengFordirectiveisactuallycreatingawebcomponenttemplatethatutilizesthesubordinatengForOfbinding,whichisapropertyofNgFor.EachinstanceinarticleswillusethetemplatetocreateaDOMsection,andwithinthissection,thearticleandindextemplatevariableswillbeavailableforinterpolation.

Page 150: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUsingdecoratorstobuildandstyleasimplecomponentdescribesthebuildingblocksofimplementinganAngular2componentPassingmembersfromaparentcomponentintoachildcomponentgoesthroughthebasicsofdownwarddataflowbetweencomponentsBindingtonativeelementattributesshowshowAngular2interfaceswithHTMLelementattributesAttachingbehaviortoDOMelementswithdirectivesdemonstrateshowtoattachbehaviortoelementswithattributedirectivesAttributepropertybindingshowsAngular2'scleverwayofdeepreferencingelementpropertiesUtilizingcomponentlifecyclehooksgivesanexampleofhowyoucanintegratewithAngular2'scomponentrenderingflow.

Page 151: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ReferencingelementsusingtemplatevariablesManydeveloperswillbeginwithAngular2andreachforsomethingthatresemblesthetrustworthyng-modelinAngular1.NgModelexistsinAngular2,butthereisanewwayofreferencingelementsinthetemplate:localtemplatevariables.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/5094/.

Page 152: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingapplicationandwantedtodirectlyaccesstheinputelement:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input>

<h1>{{title}}</h1>

`

})

exportclassArticleComponent{}

Page 153: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Angular2allowsyoutohavea#assignmentwithinthetemplateitself,whichcanconsequentlybereferencedfrominsidethetemplate.Forexample,refertothefollowingcode:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title>

<h1>{{title}}</h1>

`

})

exportclassArticleComponent{}

Withthis,youwillsee[objectHTMLInputElement](orsomethingsimilar,dependingonyourbrowser)interpolatedintothe<h1>tag.Thismeansthatthe#titleinsidethe<input>tagisnowdirectlyreferencingtheelementobject,whichofcoursemeansthatthevalueoftheelementshouldbeavailableforyou.

Don'tgettooexcitedjustyet!Ifyouattempttointerpolatetitle.valueandthenmanipulatetheinputfield,youwillnotseethebrowserupdate.ThisisbecauseAngular2nolongersupportsbidirectionaldatabindinginthisway.Fearnot,forthesolutiontothisproblemlieswithinthenewAngular2databindingpattern.

AngularwilldeclinetoupdatetheDOMuntilitthinksitneedsto.Thisneedisdeterminedbywhatbehaviorintheapplicationmightcausetheinterpolateddatatochange.Aboundevent,whichwillpropagateupwardsthroughthecomponenttree,maycauseadatachange.Thus,youcancreateaneventbindingonanelement,andthemerepresenceofthiseventbindingwilltriggerAngulartoupdatethetemplate:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title(keyup)="0">

<h1>{{title.value}}</h1>

`

})

exportclassArticleComponent{}

Here,thekeyupeventfromthetextinputisboundtoanexpressionthatiseffectivelyano-op.

Page 154: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SincetheeventwilltriggeranupdateoftheDOM,youcansuccessfullypulloutthelatestvaluepropertyfromthetitleinputelementobject.Withthis,youhavesuccessfullyboundtheinputvaluetotheinterpolatedstring.

Page 155: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Ifyouaren'tcrazyaboutthe#notation,youcanalwaysreplaceitwithval-andstillachieveidenticalbehavior:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<inputval-title(keyup)="0">

<h1>{{title.value}}</h1>

`

})

exportclassArticleComponent{}

Also,it'simportanttorecallthatthesetemplatevariablesareonlyaccessiblewithinthetemplate.Ifyouwanttopassthembacktothecontroller,you'llhavetouseitasahandlerargument:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title(keyup)="setTitle(title.value)">

<h1>{{myTitle}}</h1>

`

})

exportclassArticleComponent{

myTitle:string;

setTitle(val:string):void{

this.myTitle=val;

}

}

Page 156: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoReferencingaparentcomponentfromachildcomponentdescribeshowacomponentcangainadirectreferencetoitsparentviainjectionConfiguringmutualparent-childawarenesswithViewChildandforwardRefinstructsyouonhowtoproperlyuseViewChildtoreferencechildcomponentobjectinstancesConfiguringmutualparent-childawarenesswithContentChildandforwardRefinstructsyouonhowtoproperlyuseContentChildtoreferencechildcomponentobjectinstances

Page 157: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

AttributepropertybindingOneofthegreatnewbenefitsofthenewAngularbindingstyleisthatyouareabletomoreaccuratelytargetwhatyouarebindingto.Formerly,theHTMLattributethatwasusedasadirectiveordatatokenwassimplyusedasamatchingidentifier.Now,youareabletousepropertybindingswithinthebindingmarkupforboththeinputandoutput.

Note

Thecode,links,andaliveexampleofthisisavailableathttp://ngcookbook.herokuapp.com/8565/.

Page 158: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingapplication:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title(keydown)="setTitle(title.value)">

<h1>{{myTitle}}</h1>

`

})

exportclassArticleComponent{

myTitle:string;

setTitle(val:string):void{

this.myTitle=val;

}

}

Yourobjectiveistomodifythissothatitexhibitsthefollowingbehavior:

The<h1>tagisnotupdatedwiththevalueoftheinputfielduntiltheuserstrikestheEnterkey.Ifthe<h1>valuedoesnotmatchthevalueinthetitleinput(callthisstate"stale"),thetextcolorshouldbered.Ifitdoesmatch,itshouldbegreen.

Page 159: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...BothofthesebehaviorscanbeachievedwithAngular2'sattributepropertybinding.First,youcanchangetheeventbindingsothatonlyanEnterkeywillinvokethecallback:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title(keydown.enter)="setTitle(title.value)">

<h1>{{myTitle}}</h1>

`

})

exportclassArticleComponent{

myTitle:string;

setTitle(val:string):void{

this.myTitle=val;

}

}

Next,youcanuseAngular'sstylebindingtodirectlyassignavaluetoastyleproperty.ThisrequiresaddingaBooleantothecontrollerobjecttomaintainthestate:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title(keydown.enter)="setTitle(title.value)">

<h1[style.color]="isStale?'red':'green'">{{myTitle}}</h1>

`

})

exportclassArticleComponent{

myTitle:string='';

isStale:boolean=false;

setTitle(val:string):void{

this.myTitle=val;

}

}

Closer,butthisstillprovidesnowayofreachingastalestate.Toachievethis,addanotherkeydowneventbinding:

[app/article.component.ts]

Page 160: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<input#title

(keyup.enter)="setTitle(title.value)"

(keyup)="checkStale(title.value)">

<h1[style.color]="isStale?'red':'green'">

{{myTitle}}

</h1>

`

})

exportclassArticleComponent{

myTitle:string='';

privateisStale:boolean=false;

setTitle(val:string):void{

this.myTitle=val;

}

checkStale(val:string):void{

this.isStale=val!==this.myTitle;

}

}

Withthis,thecolorofthe<h1>tagshouldcorrectlykeeptrackofwhetherthedataisstaleornot!

Page 161: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...ThesimpleexplanationisthatAngularprovidesyouwithalotofsyntacticalsugar,butataverybasiclevelwithoutinvolvingalotofcomplexity.Ifyouweretoinspectthekeyupevent,youwouldofcoursenoticethatthereisnoenterpropertyavailable.AngularoffersyouanumberofthesepseudopropertiessothatcheckingthekeyCodeofthepressedkeyisnotnecessary.

Inasimilarway,Angularalsoallowsyoutobindtoandaccessstylepropertiesdirectly.Itisinferredthatthestylebeingaccessedreferstothehostelement.

Page 162: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Noteherethatyouhaveassignedtwohandlerstowhatisessentiallythesameevent.Notonlythis,butrearrangingtheorderofthebindingmarkupwillbreakthisapplication'sdesiredbehavior.

Note

Whentwohandlersareassignedtothesameevent,Angularwillexecutethehandlersintheorderthattheyaredefinedinthemarkup.

Page 163: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBindingtonativeelementattributesshowshowAngular2interfaceswithHTMLelementattributesRegisteringhandlersonnativebrowsereventsdemonstrateshowyoucaneasilyattachbehaviortobrowsereventsGeneratingandcapturingcustomeventsusingEventEmitterdetailshowtopropagateinformationupwardsbetweencomponentsAttachingbehaviortoDOMelementswithdirectivesdemonstrateshowtoattachbehaviortoelementswithattributedirectives

Page 164: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UtilizingcomponentlifecyclehooksAngular'scomponentrenderingprocesshasalargenumberoffacets,anddifferenttypesofdataandreferenceswillbecomeavailableatdifferenttimes.Toaccountforthis,Angular2allowscomponentstosetcallbacks,whichwillbeexecutedatdifferentpointsinthecomponent'slifecycle.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/2048/.

Page 165: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeganwiththefollowingapplication,whichsimplyallowstheadditionandremovalofarticlesfromasingleinput:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-list',

template:`

<input(keyup.enter)="add($event)">

<article*ngFor="lettitleoftitles;leti=index"

[articleTitle]="title">

<button(click)="remove(i)">X</button>

</article>

`

})

exportclassArticleListComponent{

titles:Array<string>=[];

add(e:Event):void{

this.titles.push(e.target.value);

e.target.value='';

}

remove(index:number){

this.titles.splice(index,1);

}

}

[app/article.component.ts]

import{Component,Input}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>

<ng-content></ng-content>{{articleTitle}}

</h1>

`

})

exportclassArticleComponent{

@Input()articleTitle:string;

}

Yourobjectiveistouselifecyclehookstokeeptrackoftheprocessofaddingandremovingoperations.

Page 166: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Angularallowsyoutoimporthookinterfacesfromthecoremodule.Theseinterfacesaremanifestedasclassmethods,whichareinvokedattheappropriatetime:

[app/article.component.ts]

import{Component,Input,ngOnInit,ngOnDestroy}

from'@angular/core';

@Component({

selector:'article',

template:`

<h1>

<ng-content></ng-content>{{articleTitle}}

</h1>

`

})

exportclassArticleComponentimplementsOnInit,OnDestroy{

@Input()articleTitle:string;

ngOnInit(){

console.log('created',this.articleTitle);

}

ngOnDestroy(){

console.log('destroyed',this.articleTitle);

}

}

Withthis,youshouldseelogseachtimeanewArticleComponentisaddedorremoved.

Page 167: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Differenthookshavedifferentsemanticmeanings,buttheywilloccurinawell-definedorder.Eachhook'sexecutionguaranteesthatacertainbehaviorofacomponentisjustcompleted.

Thehooksthatarecurrentlyavailabletoyouintheorderofexecutionareasfollows:

ngOnChanges

ngOnInit

ngDoCheck

ngAfterContentInit

ngAfterContentChecked

ngAfterViewInit

ngAfterContentChecked

ngOnDestroy

Itisalsopossibleforthird-partylibrariestoextendtheseandaddtheirownhooks.

Page 168: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Usinghooksisoptional,andAngularwillonlyinvokethemifyouhavedefinedthem.Theuseoftheimplementsinterfacedeclarationisoptional,butitwillsignaltotheTypeScriptcompilerthatacorrespondingmethodshouldbeexpected,whichisobviouslyagoodpractice.

Page 169: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoReferencingaparentcomponentfromachildcomponentdescribeshowacomponentcangainadirectreferencetoitsparentviainjectionConfiguringmutualparent-childawarenesswithViewChildandforwardRefinstructsyouonhowtoproperlyuseViewChildtoreferencechildcomponentobjectinstancesConfiguringmutualparent-childawarenesswithContentChildandforwardRefinstructsyouonhowtoproperlyuseContentChildtoreferencechildcomponentobjectinstances

Page 170: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ReferencingaparentcomponentfromachildcomponentInthecourseofbuildinganapplication,youmayencounterascenariowhereitwouldbeusefultoreferenceaparentcomponentfromachildcomponent,suchastoinspectmemberdataorinvokepublicmethods.InAngular2,thisisactuallyquiteeasytoaccomplish.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4907/.

Page 171: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeginwiththefollowingArticleComponent:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<feedback[val]="likes"></feedback>

`

})

exportclassArticleComponent{

likes:number=0;

incrementLikes():void{

this.likes++;

}

}

Yourobjectiveistoimplementthefeedbackcomponentsothatitdisplaysthenumberoflikespassedtoit,buttheparentcomponentcontrolstheactuallikecountandpassesthatvaluein.

Page 172: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbyimplementingthebasicstructureofthechildcomponent:

[app/feedback.component.ts]

import{Component,Input}from'@angular/core';

@Component({

selector:'feedback',

template:`

<h1>Numberoflikes:{{val}}</h1>

<button(click)="likeArticle()">Likethisarticle!</button>

`

})

exportclassFeedbackComponent{

@Input()val:number;

likeArticle():void{}

}

Sofar,noneofthisshouldsoundsurprising.Clickingonthebuttoninvokesanemptymethod,andyouwantthismethodtoinvokeamethodfromtheparentcomponent.However,youcurrentlylackareferencetodothis.Listingthecomponentinthechildcomponentconstructorwillmakeitavailabletoyou:

[app/feedback.component.ts]

import{Component,Input}from'@angular/core';

import{ArticleComponent}from'./article.component';

@Component({

selector:'feedback',

template:`

<h1>Numberoflikes:{{val}}</h1>

<button(click)="like()">Likethisarticle!</button>

`

})

exportclassFeedbackComponent{

@Input()val:number;

privatearticleComponent:ArticleComponent;

constructor(articleComponent:ArticleComponent){

this.articleComponent=articleComponent;

}

like():void{

this.articleComponent.incrementLikes();

}

}

Page 173: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Withareferencetotheparentcomponentnowavailable,youareeasilyabletoinvokeitspublicmethod,namelyincrementLikes().Atthispoint,thetwocomponentsshouldcommunicatecorrectly.

Page 174: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Verysimply,Angular2recognizesthatyouareinjectingacomponentthatistypedinthesamewayastheparent,anditwillprovidethatparentforyou.Thisisthefullparentinstance,andyouarefreetointeractwithitasyouwouldnormallyinteractwithanycomponentinstance.

Tip

Noticethatitisrequiredthatyoustoreareferencetothecomponentinsidetheconstructor.Unlikewhenyouinjectaservice,thechildcomponentwillnotautomaticallymaketheArticleComponentinstanceavailabletoyouasthis.articleComponent;youneedtodothismanually.

Page 175: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Anastutedeveloperwillnoticethatthiscreatesaveryrigiddependencyfromthechildcomponenttotheparentcomponent.Thisisindeedthecase,butnotnecessarilyabadthing.Often,itisusefultoallowcomponentstomoreeasilyinteractwitheachotherattheexpenseoftheirmodularity.Andgenerally,thiswillbeajudgmentcallonyourpart.

Page 176: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoPassingmembersfromaparentcomponentintoachildcomponentgoesthroughthebasicsofdownwarddataflowbetweencomponentsRegisteringhandlersonnativebrowsereventsdemonstrateshowyoucaneasilyattachbehaviortobrowsereventsGeneratingandcapturingcustomeventsusingEventEmitterdetailshowtopropagateinformationupwardsbetweencomponentsConfiguringmutualparent-childawarenesswithViewChildandforwardRefinstructsyouonhowtoproperlyuseViewChildtoreferencechildcomponentobjectinstancesConfiguringmutualparent-childawarenesswithContentChildandforwardRefinstructsyouonhowtoproperlyuseContentChildtoreferencechildcomponentobjectinstances

Page 177: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Configuringmutualparent-childawarenesswithViewChildandforwardRefDependingonyourapplication'sseparationofconcerns,itmightmakesenseforachildcomponentinyourapplicationtoreferenceaparent,andatthesametime,fortheparenttoreferencethechild.Therearetwosimilarimplementationsthatallowyoutoaccomplishthis:usingViewChildandContentChild.Thisrecipewilldiscussthemboth.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/1315/.

Page 178: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththerecipesetupshowninReferencingaparentcomponentfromachildcomponent.Yourobjectiveistoaddtheabilitytoenableanddisablethelikebuttonfromtheparentcomponent.

Page 179: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Theinitialsetuponlygivesthechildaccesstotheparent,whichisonlyhalfofwhatyouneed.Theotherhalfistogivetheparentaccesstothechild.

GettingareferencetoFeedbackComponentthatyouseeintheArticleComponenttemplateviewcanbedoneintwoways,andthefirstwaydemonstratedherewilluseViewChild.

ConfiguringaViewChildreference

UsingViewChildwillallowyoutoextractacomponentreferencefrominsidetheview.Moreplainly,inthisexample,usingViewChildwillgiveyoutheabilitytoreferencetheFeedbackComponentinstancefrominsidetheArticleComponentcode.

First,configureArticleComponentsothatitwillretrievethecomponentreference:

[app/article.component.ts]

import{Component,ViewChild}from'@angular/core';

import{FeedbackComponent}from'./feedback.component';

@Component({

selector:'article',

template:`

<inputtype="checkbox"

(click)="changeLikesEnabled($event)">

<feedback[val]="likes"></feedback>

`

})

exportclassArticleComponent{

@ViewChild(FeedbackComponent)

feedbackComponent:FeedbackComponent;

likes:number=0;

incrementLikes():void{

this.likes++;

}

changeLikesEnabled(e:Event):void{

this.feedbackComponent.setLikeEnabled(e.target.checked);

}

}

ThemainthemeinthisnewcodeisthattheViewChilddecoratorimplicitlyunderstandsthatitshouldtargettheviewofthiscomponent,findtheinstanceofFeedbackComponentthatisbeingrenderedthere,andassignittothefeedbackCompnentmemberoftheArticleComponentinstance.

CorrectingthedependencycyclewithforwardRef

Page 180: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Atthispoint,youshouldbeseeingyourapplicationthrowingnewerrors,mostlikelyaboutbeingunabletoresolvetheparametersforFeedbackComponent.Thisoccursbecauseyouhavesetupacyclicdependency:FeedbackComponentdependsonArticleComponentandArticleComponentdependsonFeedbackComponent.Thankfully,thisproblemexistsinthedomainofAngulardependencyinjection,soyoudon'treallyneedthemodule,justatokenthatrepresentsit.Forthispurpose,Angular2providesyouwithforwardRef,whichallowsyoutouseamoduledependencyinsideyourclassdefinitionbeforeitisdefined.Useitasfollows:

[app/feedback.component.ts]

import{Component,Input,Inject,forwardRef}

from'@angular/core';

import{ArticleComponent}from'./article.component';

@Component({

selector:'feedback',

template:`

<h1>Numberoflikes:{{val}}</h1>

<button(click)="likeArticle()">

Likethisarticle!

</button>

`

})

exportclassFeedbackComponent{

@Input()val:number;

privatearticleComponent:ArticleComponent;

constructor(@Inject(forwardRef(()=>ArticleComponent))

articleComponent:ArticleComponent){

this.articleComponent=articleComponent;

}

likeArticle():void{

this.articleComponent.incrementLikes();

}

}

Addingthedisablebehavior

Withthecycleproblemresolved,addthesetLikeEnabled()methodthattheparentcomponentisinvoking:

[app/feedback.component.ts]

import{Component,Input,Inject,forwardRef}

from'@angular/core';

import{ArticleComponent}from'./article.component';

@Component({

selector:'feedback',

Page 181: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

template:`

<h1>Numberoflikes:{{val}}</h1>

<button(click)="likeArticle()"

[disabled]="!likeEnabled">

Likethisarticle!

</button>

`

})

exportclassFeedbackComponent{

@Input()val:number;

privatelikeEnabled:boolean=false;

privatearticleComponent:ArticleComponent;

constructor(@Inject(forwardRef(()=>ArticleComponent))

articleComponent:ArticleComponent){

this.articleComponent=articleComponent;

}

likeArticle():void{

this.articleComponent.incrementLikes();

}

setLikeEnabled(newEnabledStatus:boolean):void{

this.likeEnabled=newEnabledStatus;

}

}

Withthis,togglingthecheckboxshouldenableanddisablethelikebutton.

Page 182: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...ViewChilddirectsAngulartofindthefirstinstanceofFeedbackComponentpresentinsidetheArticleComponentviewandassignittothedecoratedclassmember.Thereferencewillbeupdatedalongwithanyviewupdates.Thisdecoratedmemberwillrefertothechildcomponentinstanceandcanbeinteractedwithlikeanynormalobjectinstance.

Note

It'simportanttorememberthedualityofthecomponentinstanceanditsrepresentationinthetemplate.Forexample,FeedbackComponentisrepresentedbyafeedbacktag(pre-render)andaheadertagandabutton(post-render),butneitheroftheseformtheactualcomponent.TheFeedbackComponentinstanceisaJavaScriptobjectthatlivesinthememory,andthisistheobjectyouwantaccessto.Ifyoujustwantedareferencetothetemplateelements,thiscouldbeaccomplishedbyatemplatevariable,forexample.

Page 183: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...SinceAngularperformshierarchicalrendering,ViewChildwillnotbereadyuntiltheviewisinitialized,butrather,aftertheAfterViewInitlifecyclehook.Thiscanbedemonstratedasfollows:

[app/article.component.ts]

import{Component,ViewChild,ngAfterViewInit}

from'@angular/core';

import{FeedbackComponent}from'./feedback.component';

@Component({

selector:'article',

template:`

<inputtype="checkbox"

(click)="changeLikesEnabled($event)">

<feedback[val]="likes"></feedback>

`

})

exportclassArticleComponentimplementsAfterViewInit{

@ViewChild(FeedbackComponent)

feedbackComponent:FeedbackComponent;

likes:number=0;

constructor(){

console.log(this.feedbackComponent);

}

ngAfterViewInit(){

console.log(this.feedbackComponent);

}

incrementLikes():void{

this.likes++;

}

changeLikesEnabled(e:Event):void{

this.feedbackComponent.setLikeEnabled(e.target.checked);

}

}

Thiswillfirstlogundefinedinsidetheconstructorastheview,andtherefore,FeedbackComponentdoesnotyetexist.OncetheAfterViewInitlifecyclehookoccurs,youwillbeabletoseeFeedbackComponentloggedtotheconsole.

ViewChildren

Ifyouwouldliketogetareferencetomultiplecomponents,youcanperformanidenticalreferenceacquisitionusingViewChildren,whichwillprovideyouwithaQueryListofallthe

Page 184: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

matchingcomponentsintheview.

Tip

AQueryListcanbeusedlikeanarraywithitstoArray()method.Italsoexposesachangesproperty,whichemitsaneventeverytimeamemberofQueryListchanges.

Page 185: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUtilizingcomponentlifecyclehooksgivesanexampleofhowyoucanintegratewithAngular2'scomponentrenderingflowReferencingaparentcomponentfromachildcomponentdescribeshowacomponentcangainadirectreferencetoitsparentviainjectionConfiguringmutualparent-childawarenesswithContentChildandforwardRefinstructsyouonhowtoproperlyuseContentChildtoreferencechildcomponentobjectinstances

Page 186: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Configuringmutualparent-childawarenesswithContentChildandforwardRefThecompaniontoAngular'sViewChildisContentChild.Itperformsasimilarduty;itretrievesareferencetothetargetchildcomponentandmakesitavailableasamemberoftheparentcomponentinstance.ThedifferenceisthatContentChildretrievesthemarkupthatexistsinsidetheparentcomponent'sselectortags,whereasViewChildretrievesthemarkupthatexistsinsidetheparentcomponent'sview.

Thedifferenceisbestdemonstratedbyacomparisonofbehavior,sothisrecipewillconverttheexamplefromConfiguringMutualParent-ChildAwarenesswithViewChildandforwardReftouseContentChildinstead.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/7386/.

Page 187: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththecodefromtheConfiguringmutualparent-childawarenesswithViewChildandforwardRefrecipe.

Page 188: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beforeyoubegintheconversion,you'llneedtonesttheArticleComponenttagsinsideanotherrootcomponent,asContentChildwillnotworkfortheroot-levelbootstrappedapplicationcomponent.CreateawrappedRootComponent:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<article></article>

`

})

exportclassRootComponent{}

ConvertingtoContentChild

ContentChildisintroducedtocomponentsinessentiallythesamewayasViewChild.InsideArticleComponent,performthisconversionandreplacethe<feedback>tagwith<ng-content>:

[app/article.component.ts]

import{Component,ContentChild}from'@angular/core';

import{FeedbackComponent}from'./feedback.component';

@Component({

selector:'article',

template:`

<inputtype="checkbox"

(click)="changeLikesEnabled($event)">

<ng-content></ng-content>

`

})

exportclassArticleComponent{

@ContentChild(FeedbackComponent)

feedbackComponent:FeedbackComponent;

likes:number=0;

incrementLikes():void{

this.likes++;

}

changeLikesEnabled(e:Event):void{

this.feedbackComponent.setLikeEnabled(e.target.checked);

}

}

Page 189: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Ofcourse,thiswillonlybeabletofindthechildcomponentifthe<article></article>taghascontentinsideofit:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<article>

<feedback></feedback>

</article>

`

})

exportclassRootComponent{}

Note

You'llnoticethatthelikecountvaluebeingpassedtothechildcomponentasaninputhasbeenremoved.Verysimply,thatconventionwillnotworkanymore,asbindingitherewoulddrawthelikecountfromRootComponent,whichdoesnothavethisinformation.

Correctingdatabinding

TheFeedbackComponentwillneedtoretrievethelikecountdirectly:

[app/feedback.component.ts]

import{Component,Inject,forwardRef}from'@angular/core';

import{ArticleComponent}from'./article.component';

@Component({

selector:'feedback',

template:`

<h1>Numberoflikes:{{val}}</h1>

<button(click)="likeArticle()"

[disabled]="!likeEnabled">

Likethisarticle!

</button>

`

})

exportclassFeedbackComponent{

privateval:number;

privatelikeEnabled:boolean=false;

privatearticleComponent:ArticleComponent;

constructor(@Inject(forwardRef(()=>ArticleComponent))

articleComponent:ArticleComponent){

this.articleComponent=articleComponent;

this.updateLikes();

Page 190: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

updateLikes(){

this.val=this.articleComponent.likes;

}

likeArticle():void{

this.articleComponent.incrementLikes();

this.updateLikes();

}

setLikeEnabled(newEnabledStatus:boolean):void{

this.likeEnabled=newEnabledStatus;

}

}

That'sit!TheapplicationshouldbehaveidenticallytothesetupfromtheGettingreadysectionoftherecipe.

Page 191: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...ContentChilddoesnearlythesamethingasViewChild;itjustlooksinadifferentplace.ContentChilddirectsAngulartofindthefirstinstanceofFeedbackComponentpresentinsidetheArticleComponenttags.Here,thisstepreferstoanythingthatisinterpolatedby<ng-content>.Itthenassignsthefoundcomponentinstancetothedecoratedclassmember.Thereferenceisupdatedalongwithanyviewupdates.Thisdecoratedmemberwillrefertothechildcomponentinstanceandcanbeinteractedwithlikeanynormalobjectinstance.

Page 192: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...SinceAngularperformshierarchicalrendering,ContentChildwillnotbereadyuntiltheviewisinitialized,butrather,aftertheAfterContentInitlifecyclehook.Thiscanbedemonstratedasfollows:

[app/article.component.ts]

import{Component,ContentChild,ngAfterContentInit}

from'@angular/core';

import{FeedbackComponent}from'./feedback.component';

@Component({

selector:'article',

template:`

<inputtype="checkbox"

(click)="changeLikesEnabled($event)">

<ng-content></ng-content>

`

})

exportclassArticleComponentimplementsAfterContentInit{

@ContentChild(FeedbackComponent)

feedbackComponent:FeedbackComponent;

likes:number=0;

constructor(){

console.log(this.feedbackComponent);

}

ngAfterContentInit(){

console.log(this.feedbackComponent);

}

incrementLikes():void{

this.likes++;

}

changeLikesEnabled(e:Event):void{

this.feedbackComponent.setLikeEnabled(e.target.checked);

}

}

Thiswillfirstlogundefinedinsidetheconstructorasthecontent,andthereforeFeedbackComponentdoesnotyetexist.OncetheAfterContentInitlifecyclehookoccurs,youwillbeabletoseeFeedbackComponentloggedtotheconsole.

ContentChildren

Ifyouwouldliketogetareferencetomultiplecomponents,youcanperformanidenticalreferenceacquisitionprocessusingContentChildren,whichwillprovideyouwithQueryList

Page 193: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ofallthematchingcomponentsinsidethecomponent'stags.

Tip

AQueryListcanbeusedlikeanarraywithitstoArray()method.Italsoexposesachangesproperty,whichemitsaneventeverytimeamemberofQueryListchanges.

Page 194: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUtilizingcomponentlifecyclehooksgivesanexampleofhowyoucanintegratewithAngular2'scomponentrenderingflow.Referencingaparentcomponentfromachildcomponentdescribeshowacomponentcangainadirectreferencetoitsparentviainjection.Configuringmutualparent-childawarenesswithViewChildandforwardRefinstructsyouonhowtoproperlyuseViewChildtoreferencechildcomponentobjectinstances.

Page 195: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter3.BuildingTemplate-DrivenandReactiveFormsThischapterwillcoverthefollowingrecipes:

Implementingsimpletwo-waydatabindingwithngModelImplementingbasicfieldvalidationwithaFormControlBundlingFormControlswithaFormGroupBundlingFormControlswithaFormArrayImplementingbasicformswithngFormImplementingbasicformswithFormBuilderandformControlNameCreatingandusingacustomvalidatorCreatingandusingacustomasynchronousvalidatorwithPromises

Page 196: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionFormsareimportantelementalconstructsfornearlyeverywebapplication,andtheyhavebeenreimaginedforthebetterinAngular2.Angular1formswereveryuseful,buttheyweretotallydependentontheconventionsofngModel.Angular2'snewfoundconventionsremoveitfromngModeldependenceandofferafreshapproachtoformandinformationmanagementthatultimatelyfeelscleanerandmoreapproachable.

Fundamentally,itisimportanttounderstandwhereandwhyformsareuseful.Therearemanyplacesinanapplicationwheremultitudinousinputdemandsassociation,andformsarecertainlyusefulinthiscontext.Angular2formsarebestusedwhenvalidatingthesaidinput,especiallysowhenmultiple-fieldandcross-fieldvalidationisrequired.Additionally,Angularformsmaintainthestateofvariousformelements,allowingtheusertoreasonthe"history"ofaninputfield.

ItisalsocriticaltorememberthattheAngular2formbehavior,muchinthesamewayasitseventanddatabinding,isgettingintegratedwiththealreadyrobustbrowserformbehavior.Browsersarealreadyverycapableofsubmittingdata,recallingdatauponapagereload,simplevalidation,andotherbehaviorsthatprettymuchallformsrelyupon.Angular2doesn'tredefinethese;rather,itintegrateswiththesebehaviorsinordertolinkinotherbehaviorsanddatathatarepartofeitheraframeworkoryourapplication.

Tip

Inthischapter,beawareofthedualityoftheuseofFormsModuleandReactiveFormsModule.Theybehaveverydifferentlyandarealmostalwaysusedseparatelywhenitcomestoformconstruction.

Page 197: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Implementingsimpletwo-waydatabindingwithngModelAngular2stillhastwo-waydatabinding,butthewayitbehavesisabitdifferentthanwhatyou'reusedto.Thisrecipewillbeginwithaverysimpleexampleandthenbreakitdownintopiecestodescribewhatit'sactuallydoing.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/0771/.

Page 198: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Two-waydatabindingusesthengModeldirective,whichisincludedinFormsModule.Addthisdirectivetoyourapplicationmodule:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{FormsModule}from'@angular/forms';

import{ArticleEditorComponent}from'./article-editor.component';

@NgModule({

imports:[

BrowserModule,

FormsModule

],

declarations:[

ArticleEditorComponent

],

bootstrap:[

ArticleEditorComponent

]

})

exportclassAppModule{}

Next,fleshoutyourcomponent,whichwillhavetwoinstancesofinputboundtothesamecomponentmemberusingngModel:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<input[(ngModel)]="title">

<input[(ngModel)]="title">

<h2>{{title}}</h2>

`

})

exportclassArticleEditorComponent{

title:string;

}

That'sallthat'srequired!Youshouldseeinputmodificationsinstantlyreflectedintheotherinputaswellasin<h2>itself.

Page 199: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Whatyou'rereallydoingisbindingtotheeventandpropertythatngModelassociateswiththisinput.Whenthecomponent'stitlememberchanges,theinputisboundtothatvalueandwillupdateitsownvalue.Whentheinput'svaluechanges,itemitsanevent,whichngModelwillbindtoandextractthevaluefrombeforepropagatingittothecomponent'stitlemember.

Note

Thebanana-in-a-boxsyntax[()]issimplyindicativeofthebindingdonetoboththeinputpropertywith[]andtheinputeventswith().

Inreality,thisisshorthandforthefollowing:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<input[ngModel]="title"(ngModelChange)="title=$event">

<input[ngModel]="title"(ngModelChange)="title=$event">

<h2>{{title}}</h2>

`

})

exportclassArticleEditorComponent{

title:string;

}

Youwillfindthatthisbehavesidenticallytowhatwediscussedbefore.

Page 200: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Youmightstillfindthatthere'sabittoomuchsyntacticalsugarhappeninghereforyourtaste.You'rebindingtongModel,butsomehow,itisequivalenttotheinputvalue.Similarly,you'rebindingtongModelChangeevents,whichareallemittinga$eventthatappearstobeonlyastring.

Thisisindeedcorrect.ThengModeldirectiveunderstandswhatitisapartofandisabletointegrate[ngModel]and(ngModelChange)correctlytoassociatethedesiredbindings.

Thecoreofthesebindingsisessentiallydoingthefollowing:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<input[value]="title"(input)="title=$event.target.value">

<input[value]="title"(input)="title=$event.target.value">

<h2>{{title}}</h2>

`

})

exportclassArticleEditorComponent{

//Initializetitle,otherwiseyou'llget"undefined"

title:string='';

}

Page 201: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingsimpletwo-waydatabindingwithngModeldemonstratesthenewwayinAngular2tocontrolbidirectionaldataflowImplementingbasicfieldvalidationwithaFormControldetailsthebasicbuildingblockofanAngularformBundlingFormControlswithaFormGroupshowshowtocombineFormControlsBundlingFormControlswithaFormArrayshowshowtohandleiterableformelementsImplementingbasicformswithngFormdemonstratesAngular'sdeclarativeformconstructionImplementingbasicformswithFormBuilderandformControlNameshowshowtousetheFormBuilderservicetoquicklyputtogethernestedformsCreatingandusingacustomvalidatordemonstrateshowtocreateacustomdirectivethatbehavesasinputvalidationCreatingandusingacustomasynchronousvalidatorwithPromisesshowshowAngularallowsyoutohaveadelayedevaluationofaformstate

Page 202: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingbasicfieldvalidationwithaFormControlThesimplestformbehaviorimaginablewouldbethevalidationofasingleinputfield.Mostofthetime,utilizing<form>tagsandgoingthroughtherestoftheboilerplateisgoodpractice,butforthepurposeofcheckingasingleinput,it'spreferabletodistillthisdowntothebareminimumrequiredinordertouseinputchecking.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/4076/.

Page 203: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposethefollowingisyourinitialsetup:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<p>Articletitle(required):</p>

<inputrequired>

<button>Save</button>

<h1>{{title}}</h1>

`

})

exportclassArticleEditorComponent{

title:string;

}

Yourgoalistochangethisinawaythatclickingthesavebuttonwillvalidatetheinputandupdatethetitlememberonlyifitisvalid.

Page 204: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...ThemostelementalcomponentofAngularformsistheFormControlobject.Inordertobeabletoassessthestateofthefield,youfirstneedtoinstantiatethisobjectinsidethecomponentandassociateitwiththefieldusingtheformControldirective.FormControllivesinsideReactiveFormsModule.Additasamoduleimport:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{ReactiveFormsModule}from'@angular/forms';

import{ArticleEditorComponent}from'./article-editor.component';

@NgModule({

imports:[

BrowserModule,

ReactiveFormsModule

],

declarations:[

ArticleEditorComponent

],

bootstrap:[

ArticleEditorComponent

]

})

exportclassAppModule{}

Withthis,youcanuseFormControlinsideArticleEditorComponent.InstantiateFormControlinsidethecomponentandbindtheinputelementtoitusingtheformControldirective:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Articletitle(required):</p>

<input[formControl]="titleControl"required>

<button>Save</button>

<h1>{{title}}</h1>

`

})

exportclassArticleEditorComponent{

title:string;

titleControl:FormControl=newFormControl();

}

Page 205: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NowthatyouhavecreatedaFormControlobjectandassociateditwithaninputfield,youwillbeabletouseitsvalidationAPItocheckthestateofthefield.Allthatisleftistouseitinsidethesubmitclickhandler:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Articletitle(required):</p>

<input[formControl]="titleControl"required>

<button(click)="submitTitle()">Save</button>

<h1>{{title}}</h1>

`

})

exportclassArticleEditorComponent{

title:string;

titleControl:FormControl=newFormControl();

submitTitle():void{

if(this.titleControl.valid){

this.title=this.titleControl.value;

}else{

alert("Titlerequired");

}

}

}

Withthis,thesubmitclickhandlerwillbeabletochecktheinput'svalidationstateandvaluewiththesameobject.

Page 206: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...TheformControldirectiveservesonlytobindanexistingFormControlobjecttoaDOMelement.TheFormControlobjectthatyouinstantiateinsidethecomponentconstructorcaneitherutilizevalidationattributesinsideanHTMLtag(asisdoneinthisexample),oracceptAngularvalidatorswheninitialized;or,itcandoboth.

Note

It'sextremelyimportanttonotethatjustbecausetheFormControlobjectisinstantiated,itdoesnotmeanthatitisabletovalidatetheinputimmediately.

Withoutaninitializedvalue,anemptyinputfieldwillbeginitslifewithavalueofnull,whichinthepresenceofarequiredattributeisofcourseinvalid.However,inthisexample,ifyouweretocheckwhethertheFormControlobjectbecomesvalidimmediatelyafteryouinstantiateitintheconstructor,theFormControlobjectwoulddutifullyinformyouthatthestateisvalidsinceithasnotbeenboundtotheDOMelementyet,andtherefore,novalidationsarebeingviolated.Sincetheinputelement'sformControlbindingwillnotoccuruntilthecomponenttemplatebecomespartoftheactualDOM,youwillnotbeabletochecktheinputstateuntilthebindingiscompleteorinsidethengAfterContentCheckedlifecyclehook.Notethatthispertainstotheexampleunderconsideration.

OncetheformControldirectivecompletesthebinding,theFormControlobjectwillexistasaninputwrapper,allowingyoutouseitsvalidandvaluemembers.

Page 207: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...ThisrecipeusesReactiveFormsModule,whichissimplertounderstandsinceallofthesetupisexplicit.WhenyouuseFormsModuleinstead,youdiscoverthatalotofwhatisaccomplishedinthisrecipecouldbedoneautomaticallyforyou,suchastheinstantiationandbindingofFormControlobjects.Italsorevolvesaroundthepresenceofa<form>tag,whichisthedefactotop-levelFormControlcontainer.ThisrecipeservestodemonstrateoneofthesimplestformsofAngularformbehavior.

Validatorsandattributeduality

Asmentionedinthisrecipe,validationdefinitionscancomefromtwoplaces.Here,youusedastandardizedHTMLtagattributethatAngularrecognizesandautomaticallyincorporatesintotheFormControlvalidationspecification.YoucouldhavejustaseasilyelectedtoutilizeanAngularValidatortoaccomplishthesametaskinstead.ThiscanbeaccomplishedbyimportingAngular'sdefaultValidatorsandinitializingtheFormControlobjectwiththerequiredvalidator:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,Validators}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Articletitle(required):</p>

<input[formControl]="titleControl">

<button(click)="submitTitle()">Save</button>

<h1>{{title}}</h1>

`

})

exportclassArticleEditorComponent{

title:string;

//Firstargumentisthedefaultinputvalue

titleControl:FormControl=

newFormControl(null,Validators.required);

submitTitle():void{

if(this.titleControl.valid){

this.title=this.titleControl.value;

}else{

alert("Titlerequired");

}

}

}

Taglesscontrols

Page 208: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Asyoumightsuspect,thereisnoreasonaFormControlmustbeboundtoaDOMelement.FormControlisanelementalpieceofformlogicthatactsasanatomicpieceofstatefulinformation,whetherornotthisinformationisderivedfrom<input>.SayyouwantedtoaddaFormControlthatwouldpreventquickformsubmissionbyonlybecomingvalidafter10seconds.YoucouldexplicitlycreateaFormControlobjectthatwouldtieintothecombinedformvalidationbutwouldnotbeassociatedwithaDOMelement.

Page 209: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingsimpletwo-waydatabindingwithngModeldemonstratesthenewwayinAngular2tocontrolbidirectionaldataflowBundlingFormControlswithaFormGroupshowshowtocombineFormControlobjectsBundlingFormControlswithaFormArrayshowshowtohandleiterableformelements

Page 210: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BundlingcontrolswithaFormGroupNaturally,formsinapplicationsfrequentlyexisttoaggregatemultipleinstancesofinputintoaunifiedbehavior.Onecommonbehavioristoassesswhetheraformisvalid,whichofcourserequiresthatallofitssubfieldsarevalid.ThiswillmostcommonlybeachievedbybundlingmultipleFormControlobjectsintoaFormGroup.Thiscanbedoneindifferentways,withvaryingdegreesofexplicitness.Thisrecipecoversanentirelyexplicitimplementation,thatis,everythingherewillbecreatedand"joined"manually.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/3052.

Page 211: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeganwiththefollowingskeletonapplication:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<p>Title:<input></p>

<p>Text:<input></p>

<p><button(click)="saveArticle()">Save</button></p>

<hr/>

<p>Preview:</p>

<divstyle="border:1pxsolid#999;margin:50px;">

<h1>{{article.title}}</h1>

<p>{{article.text}}</p>

</div>

`

})

exportclassArticleEditorComponent{

article:{title:string,text:string}={};

saveArticle():void{}

}

Yourgoalistoupdatethearticleobject(andconsequentlythetemplate)onlyifalltheinputfieldsarevalid.

Page 212: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...First,addthenecessarycodetoattachnewFormControlobjectstoeachinputfieldandvalidatethemwiththebuilt-inrequiredvalidator:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Title:<input[formControl]="titleControl"></p>

<p>Text:<input[formControl]="textControl"></p>

<p><button(click)="saveArticle()">Save</button></p>

<hr/>

<p>Preview:</p>

<divstyle="border:1pxsolid#999;margin:50px;">

<h1>{{article.title}}</h1>

<p>{{article.text}}</p>

</div>

`

})

exportclassArticleEditorComponent{

article:{title:string,text:string}={};

titleControl:FormControl

=newFormControl(null,Validators.required);

textControl:FormControl

=newFormControl(null,Validators.required);

saveArticle():void{}

}

Atthispoint,youcouldindividuallyinspecteachinput'sFormControlobjectandcheckwhetheritisvalid.However,ifthisformgrowsto100fields,itwouldbecomeunbearablytedioustomaintainthem.Therefore,youcanbundletheseFormControlobjectsintoasingleFormGroupinstead:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,FormGroup,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Title:<input[formControl]="titleControl"></p>

<p>Text:<input[formControl]="textControl"></p>

Page 213: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

<p><button(click)="saveArticle()">Save</button></p>

<hr/>

<p>Preview:</p>

<divstyle="border:1pxsolid#999;margin:50px;">

<h1>{{article.title}}</h1>

<p>{{article.text}}</p>

</div>

`

})

exportclassArticleEditorComponent{

article:{title:string,text:string}={};

titleControl:FormControl

=newFormControl(null,Validators.required);

textControl:FormControl

=newFormControl(null,Validators.required);

articleFormGroup:FormGroup=newFormGroup({

title:this.titleControl,

text:this.textControl

});

saveArticle():void{}

}

FormGroupobjectsalsoexposevalidandvaluemembers,soyoucanusethesetoverifyandassigndirectlyfromtheobject:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,FormGroup,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Title:<input[formControl]="titleControl"></p>

<p>Text:<input[formControl]="textControl"></p>

<p><button(click)="saveArticle()">Save</button></p>

<hr/>

<p>Preview:</p>

<divstyle="border:1pxsolid#999;margin:50px;">

<h1>{{article.title}}</h1>

<p>{{article.text}}</p>

</div>

`

})

exportclassArticleEditorComponent{

article:{title:string,text:string}={};

titleControl:FormControl

=newFormControl(null,Validators.required);

textControl:FormControl

=newFormControl(null,Validators.required);

articleFormGroup:FormGroup=newFormGroup({

Page 214: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

title:this.titleControl,

text:this.textControl

});

saveArticle():void{

if(this.articleFormGroup.valid){

this.article=this.articleFormGroup.value;

}else{

alert("Missingfield(s)!");

}

}

Withthisaddition,yourformshouldnowbeworkingfine.

Page 215: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...BothFormControlandFormGroupinheritfromtheabstractbaseclasscalledAbstractControl.Whatthismeansforyouisthatbothofthemexposethesamebaseclassmethods,butFormGroupwillaggregateitscompositionofAbstractControlobjectstobereadfromitsownmembers.Asyoucanseeintheprecedingcode,validactsasalogicalANDoperatorforallthechildren(meaningeverysinglechildmustreturntrueforittoreturntrue);valuereturnsanobjectofthesametopologyastheoneprovidedattheinstantiationofFormGroup,butwitheachFormControlvalueinsteadoftheFormControlobject.

Note

Asyoumightexpect,sinceFormGroupexpectsanobjectwithAbstractControlproperties,youarefreetonestaFormGroupinsideanotherFormGroup.

Page 216: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...YouareabletoaccessaFormGroup'scontainedFormControlmembersviathecontrolsproperty.ThestringthatyouusedtokeytheFormControlmembers—eitheruponFormGroupinstantiation,orwiththeaddControlmethod—isusedtoretrieveit.Inthisexample,thetextFormControlobjectcouldberetrievedinsideacomponentmethodviathis.articleCtrlGroup.controls.text.

Tip

TheAngulardocumentationwarnsyoutospecificallynottomodifytheunderlyingFormControlcollectiondirectly.Thismayleadtoanundefineddatabindingbehavior.So,alwaysbesuretousetheFormGroupmembermethodsaddControlandremoveControlinsteadofdirectlymanipulatingthecollectionofFormControlobjectsthatyoupassuponinstantiation.

FormGroupvalidators

LikeControl,aFormGroupcanhaveitsownvalidators.ThesecanbeprovidedwhentheFormGroupisinstantiated,andtheybehaveinthesamewaythataFormControlvalidatorwouldbehave.ByaddingvalidatorsattheFormGrouplevel,FormGroupcanoverridethedefaultbehaviorofonlybeingvalidwhenallitscomponentsarevalidoraddingextravalidationclauses.

Errorpropagation

Angularvalidatorsnotonlyhavetheabilitytodeterminewhethertheyarevalidornot,buttheyarealsocapableofreturningerrormessagesdescribingwhatiswrong.Forexample,whentheinputfieldsareempty,ifyouweretoexaminetheerrorspropertyofthetextFormControlobjectviathis.articleCtrlGroup.controls.text.errors,itwouldreturn{required:true}.Thisisthedefaulterrormessageofthebuilt-inrequiredvalidator.However,ifyouweretoinspecttheerrorspropertyontheparentFormGroupviathis.articleCtrlGroup.errors,youwillfindittobenull.

Thismaybecounter-intuitive,butitisnotamistake.ErrormessageswillonlyappearontheFormControlinstancethatiscausingthem.Ifyouwishtoaggregateerrormessages,youwillhavetotraversethenestedcollectionsofFormControlobjectsmanually.

Page 217: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingsimpletwo-waydatabindingwithngModeldemonstratesthenewwayinAngular2tocontrolbidirectionaldataflowImplementingbasicfieldvalidationwithaFormControldetailsthebasicbuildingblockofanAngularformBundlingFormControlswithaFormArrayshowshowtohandleiterableformelements

Page 218: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BundlingFormControlswithaFormArrayYouwillmostlikelyfindthatFormGroupsaremorethancapableofservingyourneedsforthepurposeofcombiningmanyFormControlobjectsintoonecontainer.However,thereisoneverycommonpatternthatmakesitssistertype,theFormArray,extremelyuseful:variablelengthclonedinputs.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/2816/.

Page 219: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingskeletonapplication:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Tags:</p>

<ul>

<li*ngFor="lettoftagControls;leti=index">

<input[formControl]="t">

</li>

</ul>

<p><button(click)="addTag()">+</button></p>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

tagControls:Array<FormControl>=[];

addTag():void{}

saveArticle():void{}

}

Yourobjectiveistomodifythiscomponentsothatanarbitrarynumberoftagscanbeaddedandsoallthetagscanbevalidatedtogether.

Page 220: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Inmanyways,aFormArraybehavesmoreorlessidenticallytoaFormGroup.ItisimportedinthesamewayandinheritedfromAbstractControl.Also,itisinstantiatedinasimilarwayandcanaddandremoveFormControlinstances.First,addtheboilerplatetoyourapplication;thiswillallowyoutoinstantiateaninstanceofaFormArrayandpassitthearrayofFormControlobjectsalreadyinsidethecomponent.SinceyoualreadyhaveabuttonthatismeanttoinvoketheaddTagmethod,youshouldalsoconfigurethismethodtopushanewFormControlontotagControl:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,FormArray,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Tags:</p>

<ul>

<li*ngFor="lettoftagControls;leti=index">

<input[formControl]="t">

</li>

</ul>

<p><button(click)="addTag()">+</button></p>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

tagControls:Array<FormControl>=[];

tagFormArray:FormArray=newFormArray(this.tagControls);

addTag():void{

this.tagFormArray

.push(newFormControl(null,Validators.required));

}

saveArticle():void{}

}

Note

Atthispoint,it'simportantthatyoudon'tconfuseyourselfwithwhatyouareworkingwith.InsidethisArticleEditorcomponent,youhaveanarrayofFormControlobjects(tagControls)andyoualsohaveasingleinstanceofFormArray(tagFormArray).TheFormArrayinstanceisinitializedbybeingpassedthearrayofFormControlobjects,whichitwillthenbeabletomanage.

Page 221: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NowthatyourFormArrayismanagingthetag'sFormControlobjects,youcansafelyuseitsvalidator:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,FormArray,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Tags:</p>

<ul>

<li*ngFor="lettoftagControls;leti=index">

<input[formControl]="t">

</li>

</ul>

<p><button(click)="addTag()">+</button></p>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

tagControls:Array<FormControl>=[];

tagFormArray:FormArray=newFormArray(this.tagControls);

addTag():void{

this.tagFormArray

.push(newFormControl(null,Validators.required));

}

saveArticle():void{

if(this.tagFormArray.valid){

alert('Valid!');

}else{

alert('Missingfield(s)!');

}

}

}

Page 222: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Becausethetemplateisreactingtotheclickevent,youareabletouseAngulardatabindingtoautomaticallyupdatethetemplate.However,itisextremelyimportantthatyounotetheasymmetryinthisexample.ThetemplateisiteratingthroughthetagControlsarray.However,whenyouwanttoaddanewFormControlobject,youpushittotagFormArray,whichwillinturnpushittothetagControlsarray.TheFormArrayobjectactsasthemanagerofthecollectionofFormControlobjects,andallmodificationsofthiscollectionshouldgothroughthemanager,notthecollectionitself.

Tip

TheAngulardocumentationwarnsyoutospecificallynotmodifytheunderlyingFormControlcollectiondirectly.Thismayleadtoundefineddatabindingbehavior,soalwaysbesuretousetheFormArraymemberspush,insert,andremoveAtinsteadofdirectlymanipulatingthearrayofFormControlobjectsthatyoupassuponinstantiation.

Page 223: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Youcantakethisexampleonestepfurtherbyaddingtheabilitytoremovefromthislistaswell.SinceyoualreadyhavetheindexinsidethetemplaterepeaterandFormArrayoffersindex-basedremoval,thisissimpletoimplement:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,FormArray,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<p>Tags:</p>

<ul>

<li*ngFor="lettoftagControls;leti=index">

<input[formControl]="t">

<button(click)="removeTag(i)">X</button>

</li>

</ul>

<p><button(click)="addTag()">+</button></p>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

tagControls:Array<FormControl>=[];

tagFormArray:FormArray=newFormArray(this.tagControls);

addTag():void{

this.tagFormArray

.push(newFormControl(null,Validators.required));

}

removeTag(idx:number):void{

this.tagFormArray.removeAt(idx);

}

saveArticle():void{

if(this.tagFormArray.valid){

alert('Valid!');

}else{

alert('Missingfield(s)!');

}

}

}

ThisallowsyoutocleanlyinsertandremoveFormControlinstanceswhilelettingAngulardatabindingdoalloftheworkforyou.

Page 224: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingsimpletwo-waydatabindingwithngModeldemonstratesthenewwayinAngular2tocontrolbidirectionaldataflowImplementingbasicformswithngFormdemonstratesAngular'sdeclarativeformconstructionImplementingbasicformswithFormBuilderandformControlNameshowshowtousetheFormBuilderservicetoquicklyputtogethernestedforms

Page 225: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingbasicformswithNgFormThebasicdenominationsofAngularformsareFormControl,FormGroup,andFormArrayobjects.However,itisoftennotdirectlynecessarytousetheseobjectsatall;Angularprovidesmechanismswithwhichyoucanimplicitlycreateandassigntheseobjectsandattachthemtotheform'sDOMelements.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/5116/.

Page 226: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeganwiththefollowingskeletonapplication:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<p><inputplaceholder="Articletitle"></p>

<p><textareaplaceholder="Articletext"></textarea></p>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

saveArticle():void{}

}

YourobjectiveistocollectalloftheformdataandsubmititusingAngular'sformconstructs.

Page 227: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Youshouldbeginbyreorganizingthisintoanactualbrowserform.Angulargivesyoualotofdirectivesandcomponentsforthis,andimportingtheFormsModulewillgiveyouaccesstoalltheonesyouneedmostofthetime:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{FormsModule}from'@angular/forms';

import{ArticleEditorComponent}from'./article-editor.component';

@NgModule({

imports:[

BrowserModule,

FormsModule

],

declarations:[

ArticleEditorComponent

],

bootstrap:[

ArticleEditorComponent

]

})

exportclassAppModule{}

Inaddition,youshouldreconfigurethebuttonsoitbecomesanactualsubmitbutton.Thehandlershouldbetriggeredwhentheformissubmitted,soyoucanreattachthelistenertotheform'snativesubmiteventinsteadofthebutton'sclickevent.AngularprovidesanngSubmitEventEmitterontopofthisevent,sogoaheadandattachthelistenertothis:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<form(ngSubmit)="saveArticle()">

<p><inputplaceholder="Articletitle"></p>

<p><textareaplaceholder="Articletext"></textarea></p>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

saveArticle():void{}

}

Page 228: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Next,youshouldconfiguretheformtopasstheformdatatothehandlerthroughatemplatevariable.

Note

TheformelementwillhaveanNgFormobject(andinsidethis,aFormGroup)automaticallyassociatedwithitwhenyouimportFormsModuleintotheencompassingmodule.AngularcreatesandassociatestheNgForminstancebehindthescenes.

OnewayyoucanaccessthisinstanceisbyassigningthengFormdirectiveasatemplatevariable.It'sabitofsyntacticalmagic,butusing#f="ngForm"signalstoAngularthatyouwanttobeabletoreferencetheform'sNgFormfromthetemplateusingthefvariable.

Onceyoudeclarethetemplatevariable,youareabletopassthengForminstancetothesubmithandlerasanargument,specificallyassaveArticle(f).

Thisleavesyouwiththefollowing:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{NgForm}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form#f="ngForm"

(ngSubmit)="saveArticle(f)">

<p><inputplaceholder="Articletitle"></p>

<p><textareaplaceholder="Articletext"></textarea></p>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

saveArticle(f:NgForm):void{

console.log(f);

}

}

Whenyoutestthismanually,youshouldseeyourbrowserlogginganNgFormobjecteverytimeyouclickontheSavebutton.Insidethisobject,youshouldseeashinynewFormGroupandalsothengSubmitEventEmitterthatyouarelisteningto.Sofar,sogood!

DeclaringformfieldswithngModel

Youmayhavenoticedthatnoneoftheformfieldshavebeencollected.This,ofcourse,isbecauseAngularhasnotbeeninstructedtopayattentiontothem.Forthis,FormsModuleprovidesyouwithngModel,whichwilldocertainthingsforyou:

Page 229: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

InstantiateaFormControlobject.AttachittotheDOMelementthatincorporatesthengModelattribute.LocatetheFormGroupthattheelementlivesinsideandaddtoittheFormControlitjustcreated.ThestringvalueofthenameattributewillbeitskeyinsidetheFormGroup.

Note

Thislastbulletisimportant,asattemptingtousengModelwithoutanencompassingformcontrolconstructtoattachitselftowillresultinerrors.Thisformcontrolconstructcanbetheform'sFormGroupitself,oritcanevenbeachildFormGroupinstance.

Withthis,goaheadandaddngModeltoeachofthetextinputfields:

import{Component}from'@angular/core';

import{NgForm}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form#f="ngForm"

(ngSubmit)="saveArticle(f)">

<p><inputngModel

name="title"

placeholder="Articletitle"></p>

<p><textareangModel

name="text"

placeholder="Articletext"></textarea></p>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

saveArticle(f:NgForm):void{

console.log(f);

}

}

Yourformshouldnowbefullyfunctional.Inthesubmithandler,youcanverifythatFormGrouphastwoFormControlobjectsattachedtoitbyinspectingf.form.controls,whichshouldgiveyouthefollowing:

{

text:FormControl{...},

title:FormControl{...}

}

Page 230: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Inessence,youareusingthehierarchicalnatureoftheDOMtodirecthowyourFormControlarchitectureisstructured.ThetopmostNgForminstanceiscoupledwithaFormGroup;insidethis,therestoftheform'sFormControlobjectswillreside.

EachngModeldirectsitsreferencedFormControltotheFormGroupownedbytheNgForminstance.Withthisnestedstructurenowassembled,itispossibletoreadandreasonthestateoftheentireformfromtheNgFormobject.Thisbeingthecase,passingthisobjecttothesubmithandlerwillallowyoutomanageeveryaspectofforminspectionandvalidation.

Page 231: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...If,instead,youwantedtogroupsomeofthesefieldstogether,thiscanbeaccomplishedbysimplywrappingthemwithanngModelGroupdirective.SimilartongModel,thisautomaticallyinstantiatesaFormGroupandattachesittotheparentFormGroup;also,itwilladdanyenclosedFormControlorFormGroupobjectstoitself.Forexample,refertothefollowing:

[app/article.component.ts]

import{Component}from'@angular/core';

import{NgForm}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form#f="ngForm"

(ngSubmit)="saveArticle(f)">

<divngModelGroup="article">

<p><inputngModel

name="title"

placeholder="Articletitle"></p>

<p><textareangModel

name="text"

placeholder="Articletext"></textarea></p>

</div>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

saveArticle(f:NgForm):void{

console.log(f);

}

}

Now,inspectingf.form.controlswillrevealthatithasasingleFormGroupkeyedbyarticle:

{

article:FormGroup:{

controls:{

text:FormControl{...},

title:FormControl{...}

},

...

}

}

Sincethismatchesthestructureyousetupinthetemplate,itchecksout.

Page 232: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingsimpletwo-waydatabindingwithngModeldemonstratesthenewwayinAngular2tocontrolbidirectionaldataflowImplementingbasicformswithFormBuilderandformControlNameshowshowtousetheFormBuilderservicetoquicklyputtogethernestedforms

Page 233: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingbasicformswithFormBuilderandformControlNameOutofthebox,Angularprovidesawayforyoutoputtogetherformsthatdon'trelyonthetemplatehierarchyfordefinition.Instead,youcanuseFormBuildertoexplicitlydefinehowyouwanttostructuretheformobjectsandthenmanuallyattachthemtoeachinput.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/9302/.

Page 234: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeganwiththefollowingskeletonapplication:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article-editor',

template:`

<p><inputplaceholder="Articletitle"></p>

<p><textareaplaceholder="Articletext"></textarea></p>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

constructor(){}

saveArticle():void{}

}

YourobjectiveistocollectalloftheformdataandsubmititusingAngular'sformconstructs.

Page 235: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...FormBuilderisincludedinReactiveFormsModule,soyouwillneedtoimportthesetargetsintotheapplicationmodule:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{ReactiveFormsModule}from'@angular/forms';

import{ArticleEditorComponent}from'./article-editor.component';

@NgModule({

imports:[

BrowserModule,

ReactiveFormsModule

],

declarations:[

ArticleEditorComponent

],

bootstrap:[

ArticleEditorComponent

]

})

exportclassAppModule{}

Additionally,youwillneedtoinjectitintoyourcomponenttomakeuseofit.InAngular2,thiscansimplybeaccomplishedbylistingitasatypedconstructorparameter.TheFormBuilderusesthegroup()methodtoreturnthetop-levelFormGroup,whichyoushouldassigntoyourcomponentinstance.Fornow,youwillpassanemptyobjectasitsonlyargument.

Withallthis,youcanintegratethearticleGroupFormGroupintothetemplatebyattachingitinsideaformtagusingtheformGroupdirective:

[app/article-editor.component.ts]

import{Component,Inject}from'@angular/core';

import{FormBuilder,FormGroup}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form[formGroup]="articleGroup"

(ngSubmit)="saveArticle()">

<p><inputplaceholder="Articletitle"></p>

<p><textareaplaceholder="Articletext"></textarea></p>

<p><buttontype="submit">Save</button></p>

</form>

`

})

Page 236: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassArticleEditorComponent{

articleGroup:FormGroup;

constructor(@Inject(FormBuilder)formBuilder:FormBuilder){

this.articleGroup=formBuilder.group({});

}

saveArticle():void{}

}

Withallthis,youhavesuccessfullycreatedthestructureforyourform,butFormGroupisstillnotconnectedtothemultipleinput.Forthis,youwillfirstsetupthestructureofthecontrolsinsidethebuilderandconsequentlyattachthemtoeachinputtagwithformControlName,asfollows:

[app/article-editor.component.ts]

import{Component,Inject}from'@angular/core';

import{FormBuilder,FormGroup,Validators}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form[formGroup]="articleGroup"

(ngSubmit)="saveArticle()">

<p><inputformControlName="title"

placeholder="Articletitle"></p>

<p><textareaformControlName="text"

placeholder="Articletext"></textarea></p>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

articleGroup:FormGroup;

constructor(@Inject(FormBuilder)formBuilder:FormBuilder){

this.articleGroup=formBuilder.group({

title:[null,Validators.required],

text:[null,Validators.required]

});

}

saveArticle():void{

console.log(this.articleGroup);

}

}

Withthis,yourformwillhavetwoFormControlobjectsinstantiatedinsideit,andtheywillbeassociatedwithproperinputelements.WhenyouclickonSubmit,youwillbeabletoseetheinputFormControlsinsideFormGroup.However,youmayprefertonamespacetheseFormControlobjectsinsideanarticledesignation,andyoucaneasilydothisbyintroducinganngFormGroupandacorrespondinglevelofindirectioninsidetheformBuilderdefinition:

Page 237: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/article-editor.component.ts]

import{Component,Inject}from'@angular/core';

import{FormBuilder,FormGroup,Validators}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form[formGroup]="articleGroup"

(ngSubmit)="saveArticle()">

<divformGroupName="article">

<p><inputformControlName="title"

placeholder="Articletitle"></p>

<p><textareaformControlName="text"

placeholder="Articletext"></textarea></p>

</div>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

articleGroup:FormGroup;

constructor(@Inject(FormBuilder)formBuilder:FormBuilder){

this.articleGroup=formBuilder.group({

article:formBuilder.group({

title:[null,Validators.required],

text:[null,Validators.required]

})

});

}

saveArticle():void{

console.log(this.articleGroup);

}

}

Now,thetitleandtextFormControlobjectswillexistnestedinsideanarticleFormGroupandtheycanbesuccessfullyvalidatedandinspectedinthesubmithandler.

Page 238: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Asyoumightsuspect,thearrayslivinginsidetheformBuilder.groupdefinitionswillbeappliedasargumentstoaFormControlconstructor.ThisisnicesinceyoucanavoidthenewFormControl()boilerplatewhencreatingeachcontrol.ThestringthatkeystheFormControlislinkedtoitwithformControlName.BecauseyouareusingformControlNameandformGroupName,youwillneedtohavetheformBuildernestedstructurematchexactlytowhatisthereinthetemplate.

Page 239: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...ItistotallyunderstandablethathavingtoduplicatethestructureinthetemplateandtheFormBuilderdefinitionisalittleannoying.Thisisespeciallytrueinthiscase,asthepresenceofformGroupdoesn'treallyaddanyvaluablebehaviorsinceitisattachedtoaninertdivelement.Instead,youmightwanttobeabletodothisarticlenamespacegroupingwithoutmodifyingthetemplate.ThisbehaviorcanbeaccomplishedwithformControl,whosebehaviorissimilartoformModel(itbindstoanexistinginstanceonthecomponent).

Note

Notetheparadigmthatisbeingdemonstratedwiththesedifferentkindsofformdirectives.WiththingssuchasngForm,formGroup,formArray,andformControl,Angularisimplicitlycreatingandlinkingtheseinstances.IfyouchoosetonotuseFormBuildertodefinehowFormControlsbehave,thiscanbeaccomplishedbyaddingvalidationdirectivestothetemplate.Ontheotherhand,youalsohaveformModelandformControl,whichbindtotheinstancesofthesecontrolobjectsthatyoumustmanuallycreateonthecomponent.

[app/article-editor.component.ts]

import{Component,Inject}from'@angular/core';

import{FormBuilder,FormControl,FormGroup,Validators}

from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<form[formGroup]="articleGroup"

(ngSubmit)="saveArticle()">

<p><input[formControl]="titleControl"

placeholder="Articletitle"></p>

<p><textarea[formControl]="textControl"

placeholder="Articletext"></textarea></p>

<p><buttontype="submit">Save</button></p>

</form>

`

})

exportclassArticleEditorComponent{

titleControl:FormControl

=newFormControl(null,Validators.required);

textControl:FormControl

=newFormControl(null,Validators.required);

articleGroup:FormGroup;

constructor(@Inject(FormBuilder)formBuilder:FormBuilder){

this.articleGroup=formBuilder.group({

article:formBuilder.group({

title:this.titleControl,

text:this.textControl

Page 240: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

})

});

}

saveArticle():void{

console.log(this.articleGroup);

}

}

Importantly,notethatyouhavecreatedanidenticaloutputoftheoneyoucreatedearlier.titleandtextarebundledinsideanarticleFormGroup.However,thetemplatedoesn'tneedtohaveanyreferencetothisintermediateFormGroup.

Page 241: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingsimpletwo-waydatabindingwithngModeldemonstratesthenewwayinAngular2tocontrolbidirectionaldataflowImplementingbasicfieldvalidationwithaFormControldetailsthebasicbuildingblockofanAngularformImplementingbasicformswithngFormdemonstratesAngular'sdeclarativeformconstruction

Page 242: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CreatingandusingacustomvalidatorThebasicbuilt-invalidatorsthatAngularprovideswillgetyouofftheground,butifyourapplicationreliesonforms,youwillundoubtedlycometoapointwhereyouwillwanttodefineyourownvalidatorlogic.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/8574/.

Page 243: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadstartedwiththefollowingskeletonapplication:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,Validators}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<h2>PsychStudyonHumilityWinsMajorAward</h2>

<textarea[formControl]="bodyControl"

placeholder="Articletext"></textarea>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

articleBody:string='';

bodyControl:Control

=newFormControl(null,Validators.required);

saveArticle():void{

if(this.bodyControl.valid){

alert('Valid!');

}else{

alert('Invalid!');

}

}

}

Yourobjectiveistoaddanadditionalvalidationtothetextareathatwilllimititto10words.(Theeditorialstaffisbigonbrevity.)

Page 244: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...IfyoulookatthefunctionsignatureofanAbstractControl,youwillnoticethatthevalidatorargumentisjustaValidatorFn.ThisvalidatorfunctioncanbeanyfunctionthatacceptsanAbstractControlobjectasitssoleargumentandreturnsanobjectkeyedwithstringsfortheerrorobject.Thiserrorobjectactsasadictionaryoferrors,andavalidatorcanreturnasmanyerrorsasapplicable.Thevalueofthedictionaryentrycan(andshould)containmetadataaboutwhatiscausingtheerror.Iftherearenoerrorsfoundbythecustomvalidator,itshouldjustreturnnull.

Thesimplestwaytoimplementthisisbyaddingamembermethodtothecomponent:

[app/article-editor.component.ts]

exportclassArticleEditor{

articleBody:string

bodyCtrl:Control

constructor(){

this.articleBody='';

this.bodyCtrl=newControl('',Validators.required);

}

wordCtValidator(c:Control):{[key:string]:any}{

letwordCt:number=(c.value.match(/\S+/g)||[]).length;

returnwordCt<=10?

null:

{'maxwords':{'limit':10,'actual':wordCt}};

}

saveArticle(){

if(this.bodyCtrl.valid){

alert('Valid!');

}else{

alert('Invalid!');

}

}

}

Note

Here,you'reusingaregularexpressiontomatchanynon-whitespacestrings,whichcanbetreatedasa"word."YoualsoneedtoinitializetheFormControlobjecttoanemptystringsinceyouareusingthestringprototype'smatchmethod.Sincethisregularexpressionwillreturnnullwhentherearenomatches,afallback||[]clauseisaddedtoalwaysyieldsomethingthathasalengthmethod.

Nowthatthevalidatormethodisdefined,youneedtoactuallyuseitonFormControl.Angularallowsyoutobundleanarrayofvalidatorsintoasinglevalidator,evaluatingtheminorder:

[app/article-editor.component.ts]

Page 245: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Component}from'@angular/core';

import{FormControl,Validators}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<h2>PsychStudyonHumilityWinsMajorAward</h2>

<textarea[formControl]="bodyControl"

placeholder="Articletext"></textarea>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

articleBody:string='';

bodyControl:FormControl=newFormControl(null,

[Validators.required,this.wordCtValidator]);

wordCtValidator(c:FormControl):{[key:string]:any}{

letwordCt:number

=((c.value||'').match(/\S+/g)||[]).length;

returnwordCt<=10?

null:

{maxwords:{limit:10,actual:wordCt}};

}

saveArticle():void{

if(this.bodyControl.valid){

alert('Valid!');

}else{

alert('Invalid!');

}

}

}

Withthis,yourFormControlshouldnowonlybevalidwhenthereare10wordsorfewerandtheinputisnotempty.

Page 246: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...AFormControlexpectsaValidatorFnwithaspecifiedreturntype,butitdoesnotcarewhereitcomesfrom.Therefore,youwereabletodefineamethodinsidethecomponentclassandjustpassitalongwhenFormControlwasinstantiated.

TheFormControlobjectassociatedwithagiveninputmustbeabletohavevalidatorsassociatedwithit.Inthisrecipe,youfirstimplementedcustomvalidationusingexplicitassociationviatheinstantiationargumentsanddefiningthevalidatorasasimplestandaloneValidationFn.

Page 247: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Yourinnersoftwareengineershouldbetotallydissatisfiedwiththissolution.Thevalidatoryoujustdefinedcannotbeusedoutsidethiscomponentwithoutinjectingtheentirecomponent,andexplicitlylistingeveryvalidatorwheninstantiatingtheFormControlisamajorpain.

Refactoringintovalidatorattributes

AsuperiorsolutionistoimplementaformalValidatorclass.Thishasseveralbenefits:youwillbeabletoimport/exporttheclassandusethevalidatorasanattributeinthetemplate,whichobviatestheneedforbundlingvalidatorswithValidators.compose.

Yourstrategyshouldbetocreateadirectivethatcanfunctionnotonlyasanattribute,butalsoassomethingthatAngularcanrecognizeasaformalValidatorandautomaticallyincorporateitassuch.ThiscanbeaccomplishedbycreatingadirectivethatimplementstheValidatorinterfaceandalsobundlesthenewValidatordirectiveintotheexistingNG_VALIDATORStoken.

Note

Fornow,don'tworryaboutthespecificsofwhatishappeningwiththeprovidersarrayinsidethedirectivemetadataobject.Thiswillbecoveredindepthinthechapterondependencyinjection.AllthatyouneedtoknowhereisthatthiscodeisallowingtheFormControlobjectboundtotextareatoassociatethecustomvalidatoryouarebuildingwithit.

First,movethevalidationmethodtoitsowndirectivebyperformingthestepsmentionedintheprecedingparagraph:

[app/max-word-count.validator.ts]

import{Directive}from'@angular/core';

import{Validator,FormControl,NG_VALIDATORS}

from'@angular/forms';

@Directive({

selector:'[max-word-count]',

providers:[{

provide:NG_VALIDATORS,

useExisting:MaxWordCountValidator,

multi:true

}]

})

exportclassMaxWordCountValidatorimplementsValidator{

validate(c:FormControl):{[key:string]:any}{

letwordCt:number=((c.value||'')

.match(/\S+/g)||[]).length;

returnwordCt<=10?

null:

{maxwords:{limit:10,actual:wordCt}};

Page 248: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

}

Next,addthisdirectivetotheapplicationmodule:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{ReactiveFormsModule}from'@angular/forms';

import{ArticleEditorComponent}from'./article-editor.component';

import{MaxWordCountValidator}from'./max-word-count.validator';

@NgModule({

imports:[

BrowserModule,

ReactiveFormsModule

],

declarations:[

ArticleEditorComponent,

MaxWordCountValidator

],

bootstrap:[

ArticleEditorComponent

]

})

exportclassAppModule{}

Thismakesitavailabletoallthecomponentsinthismodule.What'smore,theproviderconfigurationyouspecifiedbeforeallowsyoutosimplyaddthedirectiveattributetoanyinput,andAngularwillbeabletoincorporateitsvalidationfunctionintothatFormControl.Theintegrationisasfollows:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<h2>PsychStudyonHumilityWinsMajorAward</h2>

<textarea[formControl]="bodyControl"

required

max-word-count

placeholder="Articletext"></textarea>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

articleBody:string='';

bodyControl:FormControl=newFormControl();

Page 249: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

saveArticle():void{

if(this.bodyControl.valid){

alert('Valid!');

}else{

alert('Invalid!');

}

}

}

Thisisalreadyfarsuperior.TheMaxWordCountdirectivecannowbeimportedandusedanywhereinourapplicationbysimplylistingitasadirectivedependencyinacomponent.There'snoneedfortheValidator.composenastinesswheninstantiatingaFormControlobject.

Tip

ThisisespeciallyusefulwhenyouareimplicitlycreatingtheseFormControlobjectswithformControlandotherbuilt-informdirectives,whichformanyapplicationswillbetheprimaryformutilizationmethod.Buildingyourcustomvalidatorasanattributedirectivewillintegrateseamlesslyinthesesituations.

Youshouldstillbedissatisfiedthough,asthevalidatorishardcodedtocheckfor10words.Youwouldinsteadliketoleavethisuptotheinputthatisusingit.Therefore,youshouldchangethedirectivetoacceptasingleparameter,whichwilltaketheformoftheattribute'svalue:

[app/max-word-count.validator.ts]

import{Directive}from'@angular/core';

import{Validator,FormControl,NG_VALIDATORS}

from'@angular/forms';

@Directive({

selector:'[max-word-count]',

inputs:['rawCount:max-word-count'],

providers:[{

provide:NG_VALIDATORS,

useExisting:MaxWordCountValidator,

multi:true

}]

})

exportclassMaxWordCountValidatorimplementsValidator{

rawCount:string;

validate(c:FormControl):{[key:string]:any}{

letwordCt:number=

((c.value||'').match(/\S+/g)||[]).length;

returnwordCt<=this.maxCount?

null:

{maxwords:{limit:this.maxCount,actual:wordCt}};

}

getmaxCount():number{

Page 250: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

returnparseInt(this.rawCount);

}

}

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<h2>PsychStudyonHumilityWinsMajorAward</h2>

<textarea[formControl]="bodyControl"

required

max-word-count="10"

placeholder="Articletext"></textarea>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

articleBody:string='';

bodyControl:FormControl=newFormControl();

saveArticle():void{

if(this.bodyControl.valid){

alert('Valid!');

}else{

alert('Invalid!');

}

}

}

Nowyouhavedefinedthevalueoftheattributeasaninputtothevalidator,whichyoucanthenusetoconfigurehowthevalidatorwilloperate.

Page 251: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoCreatingandusingacustomasynchronousvalidatorwithPromisesshowshowAngularallowsyoutohaveadelayedevaluationoftheformstate

Page 252: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CreatingandusingacustomasynchronousvalidatorwithPromisesAstandardvalidatoroperatesundertheassumptionthatthevalidityofacertaininputcanbecalculatedinashortamountoftimethattheapplicationcanwaittogetoverwithbeforeitcontinuesfurther.What'smore,Angularwillrunthisvalidationeverytimethevalidatorisinvoked,whichmightbequiteoftenifformvalidationisboundtorapid-fireeventssuchaskeypresses.

Therefore,itmakesgoodsensethataconstructexiststhatwillallowyoutosmoothlyhandlethevalidationproceduresthattakeanarbitraryamountoftimetoexecuteorproceduresthatmightnotreturnatall.Forthis,AngularoffersasyncValidator,whichisfullycompatiblewithPromises.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/7811/.

Page 253: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadstartedwiththefollowingskeletonapplication:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl}from'@angular/forms';

@Component({

selector:'article-editor',

template:`

<h2>NewYork-StylePizzaActuallySaucyCardboard</h2>

<textarea[formControl]="bodyControl"

placeholder="Articletext">

</textarea>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

articleBody:string='';

bodyControl:FormControl=newFormControl();

saveArticle():void{

if(this.bodyControl.valid){

alert('Valid!');

}else{

alert('Invalid!');

}

}

}

Yourobjectiveistoconfigurethisforminawaythatitwillbecomevalidonly5secondsaftertheuserenterstheinputinordertodetersimplespambots.

Page 254: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...First,createyourvalidatorclass,andinsideit,placeastaticvalidationmethod.Thisissimilartoasynchronousvalidationmethod,butitwillinsteadreturnaPromiseobject,passingtheresultdatatotheresolvemethod.TheFormControlobjectacceptstheasyncValidatorasitsthirdargument.Ifyouweren'tusinganynormalValidators,youcouldleaveitasnull.

Tip

AsyouwouldcombineseveralValidatorsintooneusingValidators.compose,asyncValidatorscanbecombinedusingValidators.composeAsync.

Createthevalidatorskeletoninitsownfile:

[app/delay.validator.ts]

import{FormControl,Validator}from'@angular/forms';

exportclassDelayValidatorimplementsValidator{

staticvalidate(c:FormControl):Promise<{[key:string]:any}>{

}

}

Thoughthevalidatordoesnotyetdoanything,youmaystilladdittothecomponent:

[app/article-editor.component.ts]

import{Component}from'@angular/core';

import{FormControl,Validators}from'@angular/forms';

import{DelayValidator}from'./delay.validator';

@Component({

selector:'article-editor',

template:`

<h2>NewYork-StylePizzaActuallySaucyCardboard</h2>

<textarea[formControl]="bodyControl"

placeholder="Articletext">

</textarea>

<p><button(click)="saveArticle()">Save</button></p>

`

})

exportclassArticleEditorComponent{

articleBody:string='';

bodyControl:FormControl=

newFormControl(null,null,DelayValidator.validate);

saveArticle():void{

if(this.bodyControl.valid){

alert('Valid!');

}else{

Page 255: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

alert('Invalid!');

}

}

}

Thevalidatormustreturnapromise,butthispromisedoesn'teverneedtoberesolved.Furthermore,you'dliketosetthedelaytoonlyonetimeperrendering.Sointhiscase,youcanjustattachthepromisetotheFormControl:

[app/delay.validator.ts]

import{FormControl,Validator}from'@angular/forms';

exportclassDelayValidatorimplementsValidator{

staticvalidate(c:FormControl):Promise<{[key:string]:any}>{

if(c.pristine&&!c.value){

returnnewPromise;

}

if(!c.delayPromise){

c.delayPromise=newPromise((resolve)=>{

setTimeout(()=>{

console.log('resolve');

resolve();

},5000);

});

}

returnc.delayPromise;

}

}

Withthisaddition,theformwillremaininvaliduntil5secondsafterthefirsttimethevalueofthetextareaischanged.

Page 256: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Asynchronousvalidatorsarehandledindependentlyviaregular(synchronous)validators,butotherthantheirinternallatencydifferences,theyultimatelybehaveinnearlytheexactsameway.TheimportantdifferenceisthatanasyncValidator,apartfromthevalidandinvalidstatesthatitshareswithanormalValidator,hasapendingstate.TheFormControlwillremaininthisstateuntilapromiseismadeindicatingtheValidatorwillreturneitherresolvesorrejects.

Note

AFormControlinapendingstateistreatedasinvalidforthepurposeofcheckingthevalidityofaggregatingconstructs,suchasFormGrouporFormArray.

IntheValidatoryoujustcreated,checkingthepristinepropertyofFormControlisafinewayofascertainingwhetherornottheformis"fresh."Beforetheusermodifiestheinput,pristineistrue;followinganymodification(evenremovingalloftheenteredtext),pristinebecomesfalse.Therefore,itisaperfecttoolinthisexample,asitallowsustohavetheFormControlmaintaintheformstatewithoutovercomplicatingtheValidator.

Page 257: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...It'scriticaltonotetheformthatthisvalidatortakes.ThevalidationmethodinsidetheDelayValidatorclassisastaticmethodandnowhereistheDelayValidatorclassbeinginstantiated.Thepurposeoftheclassismerelytohousethevalidator.Therefore,youareunabletostoreinformationinsidethisclass,sincetherearenoinstancesinwhichyoucandoso.

Tip

Inthisexample,youmightbetemptedtoaddmemberdatatothevalidatorsinceyouwanttotrackwhethertheinputhasbeenmodifiedyet.Doingsoisverymuchananti-pattern!TheFormControlobjectshouldactasyoursolesourceofstatefulinformationinthisscenario.AFormControlobjectisinstantiatedforeachinputfield,andthereforeitistheideal"datastore"withwhichyoucantrackwhattheinputisdoing.

Validatorexecution

Ifyouweretoinspectwhenthevalidatormethodisbeingcalled,youwouldfindthatitexecutesonlyonakeypressinsidetextarea.Thismayseemarbitrary,butthedefaultFormControl/inputassignmentistoevaluatethevalidatorsofFormControlonachangeeventemittedfromtheinput.FormControlobjectsexposearegisterOnChangemethod,whichletsyouhookontothesamepointthatthevalidatorswillbeevaluated.

Page 258: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoCreatingandusingacustomvalidatordemonstrateshowtocreateacustomdirectivethatbehavesasinputvalidation

Page 259: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter4.MasteringPromisesThischapterwillcoverthefollowingrecipes:

UnderstandingandimplementingbasicPromisesChainingPromisesandPromisehandlersCreatingPromisewrapperswithPromise.resolve()andPromise.reject()ImplementingPromisebarrierswithPromise.all()CancelingasynchronousactionswithPromise.race()ConvertingaPromiseintoanObservableConvertinganHTTPserviceObservableintoZoneAwarePromise

Page 260: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionInAngular1,promisesactedasstrangebirds.Theywereessentialforbuildingrobustasynchronousapplications,butusingthemseemedtocomeataprice.Theirimplementationbywayofthe$qserviceandthedualityofpromiseanddeferredobjectsseemedbizarre.Nonetheless,onceyouwereabletomasterthem,itwaseasytoseehowtheycouldbethefoundationofextremelyrobustimplementationsinthesingle-threadedevent-drivenworldofJavaScript.

Fortunately,fordeveloperseverywhere,ES6formallyembracesthePromisefeatureasacentralcomponent.SinceTypeScriptisasupersetofES6,youwillbepleasedtoknowthatyoucanwieldpromiseseverywhereinAngularwithoutextrabaggage.AlthoughObservablessubsumealotoftheutilityofferedbypromises,thereisstillverymuchaplacefortheminyourtoolkit.

Tip

BeingabletousePromisesnativelyisaprivilegeofTypeScripttoaJavaScripttranspilation.Asofnow,somebrowserssupportPromisesnatively,whilesomedonot.Goodnewsisthatifyou'rewritingyourapplicationsinTypeScriptandaretranspilingthemproperly,youdon'thavetoworryaboutthis!Really,theonlytimeyouwouldneedtoconsidertheactualtranspilationmechanicsiswhenyouneedinformationrelatedtotheperformanceorpayloadsizebenefitsofnativeimplementationsversustheirrespectivepolyfills,andthisshouldneverbeanissuefornearlyallapplications.

Page 261: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UnderstandingandimplementingbasicPromisesPromisesareveryusefulinmanyofthecoreaspectsofAngular.Althoughtheyarenolongerboundtothecoreframeworkservice,theystillmanifestthemselvesthroughoutAngular'sAPIs.TheimplementationisconsiderablysimplerthanAngular1,butthemainrhythmshaveremainedconsistent.

Note

Youcanrefertothecode,links,andaliveexampleofthisathttp://ngcookbook.herokuapp.com/5195.

Page 262: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeforeyoustartusingpromises,youshouldfirstunderstandtheproblemtheyaretryingtosolve.Withoutworryingtoomuchabouttheinternals,youcanclassifytheconceptofaPromiseintothreedistinctstages:

Initialization:IhaveapieceofworkthatIwanttoaccomplish,andIwanttodefinewhatshouldhappenwhenthisworkiscompleted.Idonotknowwhetherthisworkwillbeevercompleted;also,theworkmayeitherfailorsucceed.Pending:Ihavestartedthework,butithasnotbeencompletedyet.Completed:Theworkisfinished,andthepromiseassumesafinalstate.The"completed"stateassumestwoforms:resolvedandrejected.Thesecorrespondtosuccessandfailure,respectively.

Thereismorenuancetohowpromiseswork,butfornow,thisissufficienttogetintosomeofthecode.

Page 263: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Apromiseimplementationinoneofitssimplestformsisasfollows:

//promisesareinstantiatedwiththe'new'keyword

varpromise=newPromise(()=>{});

ThefunctionpassedtothePromiseconstructoristhepieceofworkthatisexpectedtoexecuteasynchronously.Theformaltermforthisfunctionisexecutor.

Note

ThePromiseconstructordoesn'tcareatallabouthowtheexecutorfunctionbehaves.Itmerelyprovidesitwiththetworesolveandrejectfunctions.Itisleftuptotheexecutorfunctiontoutilizethemappropriately.Notethattheexecutorfunctiondoesn'tneedtobeasynchronousatall;however,ifitisn'tasynchronous,thenyoumightnotneedaPromiseforwhatyouaretryingtoaccomplish.

Whenthisfunctionisexecuted,internallyitunderstandswhenitiscompleted;however,ontheoutside,thereisnoconstructthatrepresentstheconceptof"runthiswhentheexecutorfunctioniscompleted".Therefore,itsfirsttwoparametersaretheresolveandrejectfunctions.Thepromisewrappingtheexecutorfunctionisinthependingstateuntiloneoftheseisinvoked.Onceinvoked,thepromiseirreversiblyassumestherespectivestate.

Note

Theexecutorfunctionisinvokedimmediatelywhenthepromiseisinstantiated.Justasimportantly,itisinvokedbeforethepromiseinstantiationisreturned.Thismeansthatifthepromisereacheseitherafulfilledorrejectedstateinsidetheexecutorsynchronously,thenthereturnvalueofnewPromise(...)willbethefreshlyconstructedPromisewitharesolvedorrejectedstatus,skippingthependingstateentirely.

Thereturnvalueofexecutorisunimportant.Nomatterwhatitreturns,thepromiseconstructorwillalwaysreturnthefreshlycreatedpromise.

Thefollowingcodedemonstratesfivedifferentexamplesofwaysthatapromisecanbeinstantiated,resolved,orrejected:

//Thisexecutorispassedresolveandreject,butis

//effectivelyano-op,sothepromisep2willforever

//remaininthe'pending'state.

constp1=newPromise((resolve,reject)=>{});

//Thisexecutorinvokes'resolve'immediately,so

//p2willtransitiondirectlytothe'fulfilled'state.

constp2=newPromise((resolve,reject)=>resolve());

Page 264: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

//Thisexecutorinvokes'reject'immediately,so

//p3willtransitiondirectlytothe'rejected'state.

//Atransitiontothe'rejected'statewillalsothrow

//anexception.Thisexceptionisthrownafterthe

//executorcompletes,soanylogicfollowingthe

//invocationofrejectwillstillbeexecuted.

constp3=newPromise((resolve,reject)=>{

reject();

//Thislog()printsbeforetheexceptionisthrown

console.log('Igotrejected!');

});

//Thisexecutorinvokes'resolve'immediately,so

//p4willtransitiondirectlytothe'fulfilled'state.

//Onceapromiseexitsthe'pending'state,itcannotchange

//again,soeventhoughrejectisinvokedafterwards,the

//finalstateofp4isstill'fulfilled'.

constp4=newPromise((resolve,reject)=>{

resolve();

reject();

});

//Thisexecutorassignsitsresolvefunctiontoavariable

//intheencompassinglexicalscopesoitcanbecalled

//outsidethepromisedefinition.

varouterResolve;

constp5=newPromise((resolve,reject)=>{

outerResolve=resolve();

});

//Stateofp5is'pending'

outerResolve();

//Stateofp5is'fulfilled'

Withwhatyou'vedonesofar,youwillnotfindthepromiseconstructtobeofmuchuse;thisisbecauseallthattheprecedingcodeaccomplishesisthesettingupofthestateofasinglepromise.Therealvalueemergeswhenyousetthesubsequentstatehandlers.APromiseobject'sAPIexposesathen()method,whichallowsyoutosethandlerstobeexecutedwhenthePromisereachesitsfinalstate:

//p1isasimplepromisetowhichyoucanattachhandlers

constp1=newPromise((resolve,reject)=>{});

//p1exposesathen()methodwhichacceptsa

//resolvehandler(onFulfilled),anda

//rejecthandler(onRejected)

p1.then(

//onFulfilledisinvokedwhenresolve()isinvoked

()=>{},

//onRejectedisinvokewhenreject()isinvoked

()=>{});

Page 265: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

//Iflefthere,p1willforeverremain"pending"

//Usingthe'new'keywordstillallowsyoutocalla

//methodonthereturnedinstance,sodefiningthe

//then()handlersimmediatelyisallowed.

//

//Instantlyresolvesp2

constp2=newPromise((resolve,reject)=>resolve())

.then(

//Thismethodwillimmediatelybeinvokedfollowing

//thep2executorinvokingresolve()

()=>console.log('resolved!'));

//"resolved!"

//Instantlyrejectsp3

constp3=newPromise((resolve,reject)=>reject())

.then(

()=>console.log('resolved!'),

//Thissecondmethodwillimmediatelybeinvokedfollowing

//thep3executorinvokingreject()

()=>console.log('rejected!'));

//"rejected!"

constp4=newPromise((resolve,reject)=>reject())

//Ifyoudon'trequireuseoftheresolvehandler,

//catch()allowsyoutodefinejusttheerrorhandling

.catch(()=>console.log('rejected!'));

//executorparameterscanbecapturedoutsideitslexical

//scopeforlaterinvocation

varouterResolve;

constp5=newPromise((resolve,reject)=>{

outerResolve=resolve;

}).then(()=>console.log('resolved!'));

outerResolve();

//"resolved!"

Page 266: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...PromisesinJavaScriptconfertothedevelopertheabilitytowriteasynchronouscodeinparallelwithsynchronouscodemoreeasily.InJavaScript,thiswasformerlysolvedwithnestedcallbacks,colloquiallyreferredtoas"callbackhell."Asinglecallback-orientedfunctionmightbewrittenasfollows:

//agenericasynchronouscallbackfunction

functionasyncFunction(data,successCallback,errorCallback){

//asyncFunctionwillperformsomeoperationthatmaysucceed,

//mayfail,ormaynotreturnatall,anyofwhich

//occursinanunknownamountoftime

//thispseudo-responsecontainsasuccessboolean,

//andthereturneddataifsuccessful

asyncOperation(data,function(response){

if(response.success===true){

successCallback(response.data);

}else{

errorCallback();

}

});

};

Ifyourapplicationdoesnotdemandanysemblanceofin-orderorcollectivecompletion,thenthefollowingwillsuffice:

functionsuccessCallback(data){

//asyncFunctionsucceeded,handledataappropriately

};

functionerrorCallback(){

//asyncFunctionfailed,handleappropriately

};

asyncFunction(data1,successCallback,errorCallback);

asyncFunction(data2,successCallback,errorCallback);

asyncFunction(data3,successCallback,errorCallback);

Thisisalmostneverthecasethough.Often,yourapplicationwilleitherdemandthatthisdataisacquiredinasequence,orthatanoperationthatrequiresmultipleasynchronouslyacquiredpiecesofdataexecutesonceallofthedatahasbeensuccessfullyacquired.Inthiscase,withoutaccesstopromises,thecallbackhellemerges:

asyncFunction(data1,(foo)=>{

asyncFunction(data2,(bar)=>{

asyncFunction(data3,(baz)=>{

//foo,bar,bazcannowallbeusedtogether

combinatoricFunction(foo,bar,baz);

},errorCallback);

},errorCallback);

Page 267: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

},errorCallback);

Thisso-calledcallbackhellhereisreallyjustanattempttoserializethreeasynchronouscalls,buttheparametrictopologyoftheseasynchronousfunctionsforcesthedevelopertosubjecttheirapplicationtothisugliness.

Page 268: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Animportantpointtorememberaboutpromisesisthattheyallowyoutobreakapartacalculationintotwoparts:thepartthatunderstandswhenthepromise's"execution"hasbeencompletedandthepartthatsignalstotherestoftheprogramthattheexecutionhasbeencompleted.

DecoupledandduplicatedPromisecontrol

BecauseapromisecangiveawaythecontrolofwhodecideswherethePromisewillbemadeready,multipleforeignpartsofthecodecansetthestateofthepromise.

Apromiseinstancecanbeeitherresolvedorrejectedatmultipleplacesinsidetheexecutor::

constp=newPromise((resolve,reject)=>{

//thefollowingarepseudo-methods,eachofwhichcanbecalled

//independentlyandasynchronously,ornotatall

functioncanHappenFirst(){resolve();};

functionmayHappenFirst(){resolve();}

functionmightHappenFirst(){reject();};

});

Apromiseinstancecanalsoberesolvedatmultipleplacesoutsidetheexecutor:

varouterResolve;

constp=newPromise((resolve,reject)=>{

outerResolve=resolve;

});

//thefollowingarepseudo-methods,eachofwhichcanbecalled

//independentlyandasynchronously,ornotatall

functioncanHappenFirst(){outerResolve();};

functionmayHappenFirst(){outerResolve();}

functionmightHappenFirst(){outerResolve();};

Note

OnceaPromise'sstatebecomesfulfilledorrejected,attemptstorejectorresolvethatpromisefurtherwillbesilentlyignored.Apromisestatetransitionoccursonlyonce,anditcannotbealteredorreversed.

ResolvingaPromisetoavalue

Partofthecentralconceptofpromiseconstructsisthattheyareableto"promise"thattherewillbeavalueavailablewhenthepromiseisresolved.

Statesdonotnecessarilyhaveadatavalueassociatedwiththem;theyonlyconfertothepromiseadefinedstateofevaluation:

varresolveHandler=()=>{},

Page 269: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

rejectHandler=()=>{};

constp0=newPromise((resolve,reject)=>{

//statecanbedefinedwithanyofthefollowing:

//resolve();

//reject();

//resolve(myData);

//reject(myData);

}).then(resolveHandler,rejectHandler);

Anevaluatedpromise(resolvedorrejected)isassociatedwithahandlerforeachofthestates.Thishandlerisinvokeduponthepromise'stransitionintothatrespectivestate.Thesehandlerscanaccessthedatareturnedbytheresolutionorrejection:

constp1=newPromise((resolve,reject)=>{

//console.infoistheresolvehandler,

//console.erroristherejecthandler

resolve(123);

}).then(console.info,console.error);

//(info)123

//resettodemonstratereject()

constp2=newPromise((resolve,reject)=>{

//console.infoistheresolvehandler,

//console.erroristherejecthandler

reject(456);

}).then(console.info,console.error);

//(error)456

Delayedhandlerdefinition

Unlikecallbacks,handlerscanbedefinedatanypointinthepromiselifecycle,includingafterthepromisestatehasbeendefined:

constp3=newPromise((resolve,reject)=>{

//immediatelyresolvethepromise

resolve(123);

});

//subsequentlydefineahandler,willbeimmediately

//invokedsincepromiseisalreadyresolved

p3.then(console.info);

//(info)123

Multiplehandlerdefinition

Similartohowasingledeferredobjectcanberesolvedorrejectedatmultipleplacesintheapplication,asinglepromisecanhavemultiplehandlersthatcanbeboundtoasinglestate.For

Page 270: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

example,asinglepromisewithmultipleresolvedhandlersattachedtoitwillinvokeallthehandlersiftheresolvedstateisreached;thesameistrueforrejectedhandlers:

constp4=newPromise((resolve,reject)=>{

//Invokeresolve()after1second

setTimeout(()=>resolve(),1000);

});

constcb=()=>console.log('called');

p4.then(cb);

p4.then(cb);

//After1second:

//"called"

//"called"

PrivatePromisemembers

AnextremelyimportantdeparturefromAngular1isthatthestateofapromiseistotallyopaquetotheexecution.Formerly,youwereabletoteaseoutthestateofthepromiseusingthepseudo-private$$stateproperty.WiththeformalES6Promiseimplementation,thestatecannotbeinspectedbyyourapplication.Youcan,however,gleanthestateofapromisefromtheconsole.Forexample,inspectingapromiseinGoogleChromeyieldssomethinglikethefollowing:

Promise{

[[PromiseStatus]]:"fulfilled",

[[PromiseValue]]:123

}

Note

PromiseStatusandPromiseValueareprivateSymbols,whichareanewconstructinES6.Symbolcanbethoughtofasauniquekeythatisusefulforsettingpropertiesonobjectsthatshouldn'tbeeasilyaccessedfromelsewhere.Forexample,ifapromiseweretousethe'PromiseStatus'stringtokeyaproperty,itcouldbeeasilyusedoutsidetheobject,evenifthepropertywassupposedtoremainprivate.WithES6privatesymbols,however,asymbolisuniquewhengenerated,andthereisnogoodwaytoaccessitinsidetheinstance.

Page 271: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoChainingPromisesandPromisehandlersdetailshowyoucanwieldthispowerfulchainingconstructtoserializeasynchronousoperationsCreatingPromisewrapperswithPromise.resolve()andPromise.reject()demonstrateshowtousethecorePromiseutilities

Page 272: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ChainingPromisesandPromisehandlersMuchofthepurposeofpromisesistoallowthedevelopertoserializeandreasonaboutindependentasynchronousactions.ThiscanbeaccomplishedbyutilizingthePromisechainingfeature.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6828/.

Page 273: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Thepromisehandlerdefinitionmethodthen()returnsanotherpromise,whichcanhavefurtherhandlersdefineduponit—inahandlercalledchain:

varsuccessHandler=()=>{console.log('called');};

varp=newPromise((resolve,reject)=>{resolve();})

.then(successHandler)

.then(successHandler)

.then(successHandler);

//called

//called

//called

Chainedhandlers'datahandoff

Chainedhandlerscanpassdatatotheirsubsequenthandlersinthefollowingmanner:

varsuccessHandler=(val)=>{

console.log(val);

returnval+1;

};

varp=newPromise((resolve,reject)=>{resolve(0);})

.then(successHandler)

.then(successHandler)

.then(successHandler);

//0

//1

//2

Rejectingachainedhandler

Returningnormallyfromapromisehandler(nottheexecutor)will,bydefault,signalchildpromisestatestobecomeresolved.However,ifeithertheexecutororthesubsequenthandlersthrowanuncaughtexception,theywill,bydefault,reject;thiswillservetocatchtheexception:

constp=newPromise((resolve,reject)=>{

//executorwillimmediatelythrowanexception,forcing

//areject

throw123;

})

.then(

//childpromiseresolvedhandler

data=>console.log('resolved',data),

//childpromiserejectedhandler

data=>console.log('rejected',data));

Page 274: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

//"rejected",123

Note

Notethattheexception,hereanumberprimitive,isthedatathatispassedtotherejectionhandler.

Page 275: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...APromisereachingafinalstatewilltriggerchildpromisestofollowitinturn.Thissimplebutpowerfulconceptallowsyoutobuildbroadandfault-tolerantpromisestructuresthatelegantlymeshcollectionsofdependentasynchronousactions.

Page 276: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Thetopologyofpromiseslendsitselftosomeinterestingutilizationpatterns.

Promisehandlertrees

Promisehandlerswillexecuteintheorderthatthepromisesaredefined.Ifapromisehasmultiplehandlersattachedtoasinglestate,thenthatstatewillexecuteallitshandlersbeforeresolvingthefollowingchainedpromise:

constincr=val=>{

console.log(val);

return++val;

};

varouterResolve;

constfirstPromise=newPromise((resolve,reject)=>{

outerResolve=resolve;

});

//definefirstPromise'shandler

firstPromise.then(incr);

//appendanotherhandlerforfirstPromise,andcollect

//thereturnedpromiseinsecondPromise

constsecondPromise=firstPromise.then(incr);

//appendanotherhandlerforthesecondpromise,andcollect

//thereturnedpromiseinthirdPromise

constthirdPromise=secondPromise.then(incr);

//atthispoint,invokingouterResolve()will:

//resolvefirstPromise;firstPromise'shandlersexecutes

//resolvesecondPromise;secondPromises'shandlerexecutes

//resolvethirdPromise;nohandlersdefinedyet

//additionalpromisehandlerdefinitionorderis

//unimportant;theywillberesolvedasthepromises

//sequentiallyhavetheirstatesdefined

secondPromise.then(incr);

firstPromise.then(incr);

thirdPromise.then(incr);

//thesetupcurrentlydefinedisasfollows:

//firstPromise->secondPromise->thirdPromise

//incr()incr()incr()

//incr()incr()

//incr()

outerResolve(0);

//0

//0

//0

Page 277: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

//1

//1

//2

Note

Sincethereturnvalueofahandlerdecideswhetherornotthepromisestateisresolvedorrejected,anyofthehandlersassociatedwithapromiseisabletosetthestate—which,asyoumayrecall,canonlybesetonce.Thedefiningoftheparentpromisestatewilltriggerthechildpromisehandlerstobeexecuted.

Itshouldnowbeapparenthowthetreesofthepromisefunctionalitycanbederivedfromthecombinationofpromisechainingandhandlerchaining.Whenusedproperly,theycanyieldextremelyelegantsolutionsfordifficultanduglyasynchronousactionserializations.

catch()

Thecatch()methodisashorthandforpromise.then(null,errorCallback).Usingitcanleadtoslightlycleanerpromisedefinitions,butitisnothingmorethansyntacticalsugar:

varouterReject;

constp=newPromise((resolve,reject)=>{

outerReject=reject;

})

.catch(()=>console.log('rejected!'));

outerReject();

//"rejected"

Tip

Itisalsopossibletochainp.then().catch().Anerrorthrownbytheoriginalpromisewillpropagatethroughthepromisecreatedbythen(),causeittoreject,andreachthepromisecreatedbycatch().Itcreatesoneextralevelofpromiseindirection,buttoanoutsideobserver,itwillbehavethesame.

Page 278: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnderstandingandimplementingbasicPromisesgivesanextensiverundownofhowandwhytousePromisesCreatingPromisewrapperswithPromise.resolve()andPromise.reject()demonstrateshowtousethecorePromiseutilities

Page 279: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CreatingPromisewrapperswithPromise.resolve()andPromise.reject()Itisusefultohavetheabilitytocreatepromiseobjectsthathavealreadyreachedafinalstatewithadefinedvalue,andalsotobeabletonormalizeJavaScriptobjectsintopromises.Promise.resolve()andPromise.reject()affordyoutheabilitytoperformboththeseactions.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/9315/.

Page 280: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...LikeallotherstaticPromisemethods,Promise.resolve()andPromise.reject()returnapromiseobject.Inthiscase,thereisnoexecutordefinition.

Ifoneofthesemethodsisprovidedwithanon-promiseargument,thereturnedpromisewillassumeeitherafulfilledorrejectedstate(correspondingtotheinvokedmethod).ThismethodwillpasstheargumenttoPromise.resolve()andPromise.reject(),alongwithanycorrespondinghandlers:

Promise.resolve('foo');

//Promise{[[PromiseStatus]]:"resolved",[[PromiseValue]]:"foo"}

Promise.reject('bar');

//Promise{[[PromiseStatus]]:"rejected",[[PromiseValue]]:"bar"}

//(error)Uncaught(inpromise)bar

Theprecedingcodeisbehaviorallyequivalenttothefollowing:

newPromise((resolve,reject)=>resolve('foo'));

//Promise{[[PromiseStatus]]:"resolved",[[PromiseValue]]:"foo"}

newPromise((resolve,reject)=>reject('bar'));

>>Promise{[[PromiseStatus]]:"rejected",[[PromiseValue]]:"bar"}

//(error)Uncaught(inpromise)bar

Promisenormalization

Promise.resolve()willuniquelyhandlescenarioswhereitispassedwithapromiseobjectasitsargument.Promise.resolve()willeffectivelyoperateasano-op,returningtheinitialpromiseargumentwithoutanymodification.Itwillnotmakeanattempttocoercetheargumentpromise'sstate:

consta=Promise.resolve('baz');

console.log(a);

//Promise{status:'resolved',value:'baz'}

constb=Promise.resolve(a);

console.log(b);

//Promise{status:'resolved',value:'baz'}

console.log(a===b);

//true

constc=Promise.reject('qux');

//Errorqux

console.log(c)

//Promise{status:'rejected',value:'qux'}

Page 281: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

constd=Promise.resolve(c);

console.log(d);

//Promise{status:'rejected',value:'qux'}

console.log(c===d);

//true

Page 282: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...WhenthinkingaboutPromisesinthecontextofthem"promising"toeventuallyassumeavalue,thesemethodsaresimplyamelioratinganylatentperiodseparatingthependingandfinalstates.

Thedichotomyisverysimple:

Promise.reject()willreturnarejectedpromisenomatterwhatitsargumentis.Evenifitisapromiseobject,thevalueofthereturnedpromisewillbethatofthepromiseobject.Promise.resolve()willreturnafulfilledpromisewiththewrappedvalueifthatvalueisnotapromise.Ifitisapromise,itbehavesasano-op.

Page 283: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Importantly,thebehaviorofPromise.resolve()isnearlythesameashow$q.when()operatedinAngular1.$q.when()wasabletonormalizepromiseobjects,butitwouldalwaysreturnanewlycreatedpromiseobject:

//Angular1

consta=$q(()=>{});

console.log(a);

//Promise{...}

constb=$q.when(a);

console.log(b);

//Promise{...}

console.log(a===b);

//false

Page 284: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnderstandingandimplementingbasicPromisesgivesanextensiverundownofhowandwhytousePromisesImplementingPromisebarrierswithPromise.all()showyouhowPromisescanbecomposableCancelingasynchronousactionswithPromise.race()guidesyouthroughtheprocessofimplementingazero-failure-tolerantPromisesystem

Page 285: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingPromisebarrierswithPromise.all()Youmayfindyourapplicationrequirestheuseofpromisesinanall-or-nothingtypeofsituation.Thatis,itwillneedtocollectivelyevaluateagroupofpromises,andthiscollectionwillresolveasasinglepromiseifandonlyifallofthecontainedpromisesareresolved;ifanyoneofthemisrejected,theaggregatepromisewillberejected.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/8496/.

Page 286: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...ThePromise.all()methodacceptsaniterablecollectionofpromises(forexample,anarrayofPromiseobjectsoranobjectwithanumberofpromiseproperties),anditwillattempttoresolveallofthemasasingleaggregatepromise.Theparameteroftheaggregateresolvedhandlerwillbeanarrayorobjectthatmatchestheresolvedvaluesofthecontainedpromises:

varouterResolveA,outerResolveB;

constpromiseA=newPromise((resolve,reject)=>{

outerResolveA=resolve;

});

constpromiseB=newPromise((resolve,reject)=>{

outerResolveB=resolve;

});

constmultiPromiseAB=Promise.all([promiseA,promiseB])

.then((values)=>console.log(values));

outerResolveA(123);

outerResolveB(456);

//[123,456]

Ifanyofthepromisesinthecollectionarerejected,theaggregatepromisewillberejected.Theparameteroftheaggregaterejectedhandlerwillbethereturnedvalueoftherejectedpromise:

varouterResolveC,outerRejectD;

constpromiseC=newPromise((resolve,reject)=>{

outerResolveC=resolve;

});

constpromiseD=newPromise((resolve,reject)=>{

outerRejectD=reject;

});

constmultiPromiseCD=Promise.all([promiseC,promiseD])

.then(

values=>console.log(values),

rejectedValue=>console.error(rejectedValue));

//resolveacollectionpromise,nohandlerexecution

outerResolveC(123);

//rejectacollectionpromise,rejectionhandlerexecutes

outerRejectD(456);

//(error)456

Page 287: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Asdemonstrated,theaggregatepromisewillreachthefinalstateonlywhenalloftheenclosedpromisesareresolved,orwhenasingleenclosedpromiseisrejected.Usingthistypeofpromiseisusefulwhenthecollectionofpromisesdonotneedtoreasonaboutoneanother,butcollectivecompletionistheonlymetricofsuccessforthegroup.

Inthecaseofacontainedrejection,theaggregatepromisewillnotwaitfortheremainingpromisestocomplete,butthosepromiseswillnotbepreventedfromreachingtheirfinalstate.Onlythefirstpromisetoberejectedwillbeabletopassrejectiondatatotheaggregatepromiserejectionhandler.

Page 288: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Promise.all()isinmanywaysextremelysimilartoanoperating-system-levelprocesssynchronizationbarrier.Aprocessbarrierisacommonpointinthethreadinstructionexecutionthatacollectionofprocesseswillreachindependentlyandatdifferenttimes,andnoprocesscanproceedfurtheruntilallhavereachedthispoint.Inthesameway,Promise.all()willnotproceedunlesseitherallofthecontainedpromiseshavebeenresolved—reachedthebarrier—orasinglecontainedrejectionwillpreventthatstatefromeverbeingachieved,inwhichcasethefailoverhandlerlogicwilltakeover.

SincePromise.all()allowsyoutohavearecombinationofpromises,italsoallowsyourapplication'sPromisechainstobecomeadirectedacyclicgraph(DAG).Thefollowingisanexampleofapromiseprogressiongraphthatdivergesfirstandconvergeslater:

Page 289: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Thislevelofcomplexityisuncommon,butitisavailableforuseshouldyourapplicationrequireit.

Page 290: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoCreatingPromiseWrapperswithPromise.resolve()andPromise.reject()demonstrateshowtousethecorePromiseutilitiesCancelingasynchronousactionswithPromise.race()guidesyouthroughtheimplementationofazero-failure-tolerantPromisesystem

Page 291: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CancelingasynchronousactionswithPromise.race()ES6introducesPromise.race(),whichisabsentfromthe$qspecinAngular1.LikePromise.all(),thisstaticmethodacceptsaniterablecollectionofpromiseobjects;whicheveroneresolvesorrejectsfirstwillbecometheresultofthepromisewrappingthecollection.Thismayseemlikeunusualbehavior,butitbecomesquiteusefulwhenyou'rebuildingacancellationbehaviorintothesystem.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4362/.

Page 292: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoustartedwithasimplepromisethatresolvestoavalueafter3seconds:

constdelayedPromise=newPromise((resolve,reject)=>

setTimeout(resolve.bind(null,'foobar'),3000))

.then(val=>console.log(val));

Youwouldliketohavetheabilitytodetachapartofyourapplicationfromwaitingforthispromise.

Page 293: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Asimplesolutionwouldbetoexposethepromise'srejecthandlerandjustinvokeitfromwhateveristoperformthecancelation.However,itispreferabletostopwaitingforthispromiseinsteadofdestroyingit.

Note

AconcreteexampleofthiswouldbeaslowbutcriticalHTTPrequestthatyourapplicationmakes.YoumightnotwanttheUItowaitforittocomplete,butyoumayhaveresolvehandlersattachedtotherequestthatyoustillwanttohandletheresult,onceitisreturned.

Instead,youcantakeadvantageofPromise.race()andintroduceacancellationpromisealongsidetheoriginalone:

//Usethismethodtocapturethecancellationfunction

varcancel;

constcancelPromise=newPromise((resolve,reject)=>{

cancel=reject;

});

constdelayedPromise=newPromise((resolve,reject)=>

setTimeout(resolve.bind(null,'foobar'),3000));

//Promise.race()createsanewpromise

Promise.race([cancelPromise,delayedPromise])

.then(

val=>console.log(val),

()=>console.error('cancelled!'));

//Ifyouinvokecancel()before3secondselapses

//(error)"cancelled!"

//Instead,if3secondselapses

//"foobar"

Now,ifdelayedPromiseresolvesfirst,thepromisecreatedbyPromise.race()willlogthevaluepassedtoithere,foobar.If,however,youinvokecancel()beforeithappens,thenthatsamePromisewillprintacancelled!error.

Page 294: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Promise.race()justwaitsforanyofitsinnerpromisestoarriveatthefinalstate.Itcreatesandreturnsanewpromisethatisbeholdentothestateofthecontainedpromises.Whenitobservesthatanyofthemtransitionstothefinalstate,thenewpromisealsoassumesthisstate.

Note

Inthisexample,theexecutorofcancelPromiseanddelayedPromiseareinvokedbeforePromise.race()iscalled.Sincepromisesonlycareaboutthestateofotherpromises,itisn'timportantthatthepromisespassedtoPromise.race()needtobealreadytechnicallystarted.

NotethattheuseofPromise.race()doesn'taffecttheimplementationofdelayedPromise.Evenwhencancel()isinvoked,delayedPromisewillstillberesolvedanditshandlerswillstillbeexecutednormally,unawarethatthesurroundingPromise.race()hasalreadybeenrejected.YoucanprovethistoyourselfbyaddingaresolvehandlertodelayedPromise,invokingcancel()andseeingtheresolvehandlerofdelayedPromisebeingexecutedanyway:

varcancel;

constcancelPromise=newPromise((resolve,reject)=>{

cancel=reject;

});

constdelayedPromise=newPromise((resolve,reject)=>

setTimeout(resolve.bind(null,'foobar'),3000))

.then(()=>console.log('stillresolved!'));

Promise.race([cancelPromise,delayedPromise])

.then(

val=>console.log(val),

()=>console.error('cancelled!'));

cancel();

//(error)cancelled!

//After3secondselapses

//"stillresolved!"

Page 295: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoCreatingPromisewrapperswithPromise.resolve()andPromise.reject()demonstrateshowtousethecorePromiseutilitiesImplementingPromisebarrierswithPromise.all()showyouhowPromisescanbecomposable

Page 296: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConvertingaPromiseintoanObservableObservablesandPromisesservedifferentpurposesandaregoodatdifferentthings,butinaspecificpartofanapplication,youwillalmostcertainlywanttobedealingwithasingledenomination.Thismeansconvertingobservablesintopromisesandviceversa.ThankstoRxJS,thisisquitesimple.

Tip

FormoreonRxJSObservables,refertoChapter5,ReactiveXObservables,whichcoversthemindepth.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/5244/.

Page 297: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...ThereisagooddealofparitybetweenPromiseandObservable.Therearediscretesuccessanderrorcases,andtheconceptofsuccessfulcompletiononlycorrespondstothesuccesscase.

RxJSobservablesexposeafromPromisemethod,whichwrapsPromiseasanObservable:

import{Observable}from'rxjs/Rx';

varouterResolve,outerReject;

constp1=newPromise((resolve,reject)=>{

outerResolve=resolve;

outerReject=reject;

});

varo1=Observable.fromPromise(p1);

NowthatyouhaveanObservableinstance,youcanutilizeitssubscribe()events,whichcorrespondtothestateofthePromiseinstance:

import{Observable}from'rxjs/Rx';

varouterResolve,outerReject;

constp1=newPromise((resolve,reject)=>{

outerResolve=resolve;

outerReject=reject;

});

varo1=Observable.fromPromise(p1);

o1.subscribe(

//onNexthandler

()=>console.log('resolved!'),

//onErrorhandler

()=>console.log('rejected'),

//onCompletedhandler

()=>console.log('finished!'));

outerResolve();

//"resolved!"

//"finished!"

Page 298: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...ThenewObservableinstancedoesn'treplacethepromise.ItjustattachesitselftothePromise'sresolvedandrejectedstates.Whenthishappens,itemitseventsandinvokestherespectivecallbacks.TheObservableinstanceisboundtothestateofthePromise,butPromiseisnotawarethatanythinghasbeenattachedtoitsinceitblindlyexposesitsresolveandrejecthooks.

Tip

NotethatonlyaresolvedPromisewillinvoketheonCompletedhandler;rejectingthepromisewillnotinvokeit.

Page 299: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...ObservablesandPromisesareinterchangeableifyouaresoinclined,butdoconsiderthattheyarebothappropriateindifferentsituations.

Observablesaregoodatstream-typeoperations,wherethelengthofthestreamisindeterminate.ItiscertainlypossibletohaveanObservablethatonlyeveremitsoneevent,butanObservablewillnotbroadcastthisstatetolistenersthatareattachedlater,unlessyouconfigureittodoso(suchasBehaviorObservable).

Promisesaregoodatmaskingasynchronousbehavior.TheyallowyoutowritecodeandsethandlersuponthePromiseasifthepromisedstateorvaluewasrealizedatthetimeofexecution.Ofcourseit'snot,buttheabilitytodefineahandlersynchronouslyatruntimeandhavethePromiseinstancedecidewhenit'sappropriatetoexecuteit,aswellastheabilitytochainthesehandlers,isextremelyvaluable.

Page 300: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnderstandingandimplementingbasicPromisesgivesanextensiverundownofhowandwhytousePromisesConvertinganHTTPserviceObservableintoZoneAwarePromisegivesyouanAngular-centricviewofhowPromises,Observables,andZonesintegrateinsideanAngularapplication

Page 301: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConvertinganHTTPserviceObservableintoaZoneAwarePromiseInAngular2,theRxJSasynchronousobservablesarefirst-classcitizensandmuchofthecoretoolkithasbeenconfiguredtorelyuponthem.Nonetheless,itisstillvaluabletobeabletohaveconversionbetweenthem,especiallysincetheyhavesimilarduties.

Tip

FormoreonRxJSObservables,refertoChapter5,ReactiveXObservables,whichcoversthemindepth.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/0905/.

Page 302: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyYou'llbeginwiththefollowingsimplisticapplication:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Http}from'@angular/http';

@Component({

selector:'article',

template:`

<p></p>

`

})

exportclassArticleComponent{

constructor(privatehttp:Http){

//Fordemopurposes,havethisplunkrequestitselfto

//avoidcrossoriginerrors

console.log(

http.get('//run.plnkr.co/plunks/TBtcNDRelAOHDVpIuWw1'));

}

}

//Observable{...}

SupposeyourgoalwastoconvertthisHTTPcalltousepromisesinstead.

Page 303: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Forthepurposesofthisrecipe,youdon'treallyneedtounderstandanydetailsaboutthehttpserviceorRxJSasynchronousobservables.Allthatyouneedtoknowisthatanymethodexposedbythehttpservicewillreturnanobservable.Happily,theRxJSimplementationcanexposea.toPromise()methodthatconvertstheobservableintoitsequivalentPromiseobject:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Http}from'@angular/http';

import'rxjs/Rx';

@Component({

selector:'article',

template:`

<p></p>

`

})

exportclassArticleComponent{

constructor(privatehttp:Http){

//Fordemopurposes,havethisplunkrequestitselfto

//avoidcrossoriginerrors

console.log(

http.get('//run.plnkr.co/plunks/TBtcNDRelAOHDVpIuWw1')

.toPromise());

}

}

//ZoneAwarePromise{...}

Page 304: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...TheHTTPservice,bydefault,returnsanobservable;however,withouttheimportedRxmodule,thiswillthrowanerror,sayingitcannotfindthetoPromise()method.

TheRxmoduleconferstoanobservabletheabilitytoconvertitselfintoapromiseobject.Angular2isintentionallynotutilizingtheObservablespecwithalltheRxJSoperatorstoallowyoutospecifyexactlywhichonesyouwant.Becausetheoperatorsexistasseparatemodules,thisleadstoasmallerpayloadsenttothebrowser.

Oncethe.toPromise()methodisinvoked,theobjectiscreatedtoaZoneAwarePromiseinstance.

Note

Thissoundsgnarly,butreally,it'sjustwrappingthePromiseimplementationaszone.jssothattheAngularzoneisawareofanyactionsthePromisecouldcausethatitshouldbeawareof.Foryourpurposes,thiscanbetreatedasaregularPromise.

Page 305: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnderstandingandimplementingbasicPromisesgivesanextensiverundownofhowandwhytousePromisesConvertingaPromiseintoanObservablegivesyouanexampleofhowRxJScanbeusedtoconvertbetweenthesetwopowerfultypes

Page 306: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter5.ReactiveXObservablesThischapterwillcoverthefollowingrecipes:

BasicutilizationofObservableswithHTTPImplementingaPublish-SubscribemodelusingSubjectsCreatinganObservableAuthenticationServiceusingBehaviorSubjectsBuildingageneralizedPublish-Subscribeservicetoreplace$broadcast,$emit,and$onUsingQueryListsandObservablestofollowthechangesinViewChildrenBuildingafullyfeaturedAutoCompletewithObservables

Page 307: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionBeforeyougetintothemeatofAngular2Observables,itisimportanttofirstunderstandtheproblemyouaretryingtosolve.

Afrequentlyencounteredscenarioinsoftwareiswhereyouareexpectingsomeentitytobroadcastthatsomethinghappened;let'scallthisan"event"(distinctfromabrowserevent).Youwouldliketohookintothisentityandattachbehaviortoitwheneveraneventoccurs.Youwouldalsoliketobeabletodetachfromthisentitywhenyounolongercareabouttheeventsitisbroadcasting.

ThereismorenuanceandadditionalcomplexitytoObservablesthatthischapterwillcover,butthisconceptofeventsunderscoresthefundamentalpatternthatisusefultoyouasthedeveloper.

Page 308: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

TheObserverPatternTheObserverPatternisn'talibraryorframework.ItisjustasoftwaredesignpatternuponwhichReactiveXObservablesarebuilt.Manylanguagesandlibrariesimplementthispattern,andReactiveXisjustoneoftheseimplementations;however,ReactiveXistheonethatAngular2hasformallyincorporatedintoitself.

TheObserverPatterndescribestherelationshipbetweensubject,whichwasdescribedasthe"entity"earlier,anditsobservers.Thesubjectisawareofanyobserversthatarewatchingit.Whenaneventisemitted,thesubjectisabletopassthiseventtoeachobserverviamethodsthatareprovidedwhentheobserverbeginstosubscribeit.

Page 309: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ReactiveXandRxJSTheReactiveXlibraryisimplementedinnumerouslanguages,includingPython,Java,andRuby.RxJS,theJavaScriptimplementationoftheReactiveXlibrary,isthedependencythatAngular2utilizestoincorporateObservablesintonativeframeworkbehavior.SimilartoPromises,youcancreateastandaloneObservableinstancethroughdirectinstantiation,butmanyAngular2methodsandserviceswillalsoutilizeanObservableinterfacebydefault.

Page 310: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ObservablesinAngular2Angular2integratesObservablesinawidevarietyofways.Ifyouarenewtothem,youmayinitiallyfeeloddusingthem.However,itisimportantyourecognizethatObservablesprovideasuperiorsoftwaredevelopmentpattern.

AlongwiththebulkRxJSmodulerxjs/Rx,youarealsoprovidedwiththestrippeddownObservablemodulerxjs/Observable.Thisminimalmoduleallowsindividualpiecesofnon-essentialbehaviortobeimportedasrequiredinordertoreducemodulebloat.Forexample,whenusingthislightweightObservablemodule,usingoperatorsorothersuchReactiveXconventionsnecessitatesthatyouexplicitlyincorporatethesemodules,inordertoextendtheavailableObservableinterface.

Page 311: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ObservablesandPromisesBothObservablesandPromisesoffersolutionstoasynchronousconstructs,butObservablesaremorerobust,extensible,anduseful.AlthoughPromisesareavailablebydefaultintheES6specification,youwillquicklyrealizethattheybecomebrittlewhenyouattempttoapplythemoutsidetherealmofbasicapplicationbehavior.

TheReactiveXlibraryofferspowerfultoolingtowhichPromisescannotcompare.Observablesarecomposable,allowingyoutotransformandcombinethemintonewObservables.Theyalsoencapsulatetheconceptofacontinuousstreamofevents-aparadigmthatisencounteredinclient-sideprogrammingextremelyfrequentlyandthatPromisesdonottranslatewellto.

Page 312: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BasicutilizationofObservableswithHTTPInAngular2,theHttpmodulenowbydefaultutilizestheObservablepatterntowrapXMLHttpRequest.Fordevelopersthatarefamiliarwiththepattern,itreadilytranslatestotheasynchronousnatureofrequeststoremoteresources.Fordevelopersthatarenewertothepattern,learningtheinsandoutsofHttpObservablesisagoodwaytowrapyourheadaroundthisnewparadigm.

Note

Thecode,links,andaliveexamplerelatedtothisareavailableathttp://ngcookbook.herokuapp.com/4121.

Page 313: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyForthepurposeofthisexample,you'lljustserveastaticJSONfiletotheapplication.HowevernotethatthiswouldbenodifferentifyouweresendingrequeststoadynamicAPIendpoint.

Beginbycreatingaskeletoncomponent,includingallthenecessarymodulesformakingHTTPrequests:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Http}from'@angular/http';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>{{author}}</p>

`

})

exportclassArticleComponent{

title:string;

body:string;

constructor(privatehttp:Http){

}

}

Forthisexample,assumethereisaJSONfileinsidethestaticdirectorynamedarticle.json:

[article.json]

{

"title":"OrthopedicDoctorsAskCityforMoreSidewalkCracks",

"author":"JakeHsu"

}

Page 314: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...SinceyouhavealreadyinjectedtheHttpservice,youcanbeginbydefiningthegetrequest:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Http}from'@angular/http';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>{{author}}</p>

`

})

exportclassArticleComponent{

title:string;

body:string;

constructor(privatehttp_:Http){

http_.get('static/article.json');

}

}

ThiscreatesanObservableinstance,butyoustillneedtoaddinstructionsonhowtohandletherawstringoftheresponse.

Note

Atthispoint,youwillnoticethatthisdoesnotactuallyfireabrowserGETrequest.Thisiscoveredinthisrecipe'sThere'smoresection.

SinceyouknowtherequestwillreturnJSON,youcanutilizethejson()methodthataResponsewouldexpose.Thiscanbedoneinsidethemap()method.However,theObservabledoesnotexposethemap()methodbydefault,soyoumustimportitfromtherxjsmodule:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Http}from'@angular/http';

import'rxjs/add/operator/map';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>{{author}}</p>

`

})

exportclassArticleComponent{

Page 315: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

title:string;

author:string;

constructor(privatehttp_:Http){

http_.get('static/article.json')

.map(response=>response.json());

}

}

Sofarsogood,butyou'restillnotdone.TheprecedingcodewillcreatetheObservableinstance,butyoustillhavetosubscribetoitinordertohandleanydataitwouldemit.Thiscanbeaccomplishedwiththesubscribe()method,whichallowsyoutoattachthecallbackanderrorhandlingmethodsofobserver:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Http}from'@angular/http';

import'rxjs/add/operator/map';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>{{author}}</p>

`

})

exportclassArticleComponent{

title:string;

author:string;

constructor(privatehttp_:Http){

http_.get('static/article.json')

.map(response=>response.json())

.subscribe(

article=>{

this.title=article.title;

this.author=article.author;

},

error=>console.error(error));

}

}

Withallofthis,theGETrequestwillreturntheJSONfile,andtheresponsedatawillbeparsedanditsdatainterpolatedintotheDOMbythecomponent.

Page 316: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Theprevioussectiongaveagoodhigh-leveloverviewofwhatwashappening,butitisusefultobreakthingsdownmorecarefullytounderstandwhateachindividualstepaccomplishes.

Observable<Response>

TheHttpserviceclassexposesthemethodsget(),post(),put(),andsoon—alltheHTTPverbsthatyouwouldexpect.EachofthesewillreturnObservable<Response>,whichwillemitaResponseinstancewhentherequestisreturned:

console.log(http_.get('static/article.json'));

//Observable{...}

Note

Itsoundsobvious,butObservablesareobservedbyanobserver.TheobserverwillwaitforObservabletoemitobjects,whichinthisexampletakestheformofResponse.

TheRxJSmap()operator

TheResponseinstanceexposesajson()method,whichconvertsthereturnedserializedpayloadstringintoitscorrespondingin-memoryobjectrepresentation.Youwouldliketobeabletopassaregularobjecttotheobserverhandler,sotheidealtoolhereisawedgemethodthatstillgivesyouanObservableintheend:

console.log(http_.get('static/article.json')

.map(response=>response.json()));

//Observable{source:Observable,operator:MapOperator,...}

RecallthatthecanonicalformofObservablesisastreamofevents.Inthiscase,weknowtherewillonlyeverbeoneevent,whichistheHTTPresponse.Nonetheless,allthenormaloperatorsthatwouldbeusedonastreamofeventscanjustaseasilybeusedonthissingle-eventObservable.

InthesamewaythatArray.map()canbeusedtotransformeachinstanceinthearray,Observable.map()allowsyoutotransformeacheventemittedfromObservable.Morespecifically,itcreatesanotherObservablethatemitsthemodifiedeventpassedfromtheinitialobservable.

Subscribe

Observableinstancesexposeasubscribe()methodthatacceptsanonNexthandler,anonErrorhandler,andanonCompletedhandlerasarguments.ThesehandlerscorrespondtotheeventsinthelifecycleoftheObservablewhenitemitsResponseinstances.TheparameterfortheonNextmethodiswhateverisemittedfromtheObservable.Inthiscase,theemitteddatais

Page 317: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

thereturnedvaluefrommap(),soitwillbetheparsedobjectthathasreturnedafterinvokingjson()ontheResponseinstance.

Allthesemethodsareoptional,butinthisexample,theonNextandonErrormethodsareuseful.

Note

Together,thesemethodswhenprovidedtosubscribe()constitutewhatisidentifiedastheobserver.

http_.get('static/article.json')

.map(respose=>respose.json())

.subscribe(

article=>{

this.title=article.title;

this.body=article.body;

},

error=>console.error(error));

Withallofthistogether,thebrowserwillfetchtheJSONandparseit,andthesubscriberwillpassitsdatatotherespectivecomponentmembers.

Page 318: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Whenconstructingthisrecipepiecebypiece,ifyouarewatchingyourbrowser'snetworkrequestsasyouassembleit,youwillnoticethattheactualGETrequestisnotfireduntilthesubscribe()methodisinvoked.ThisisbecausethetypeObservableyouareusingis"cold".

HotandcoldObservables

The"cold"designationmeansthattheObservabledoesnotbegintoemituntilanobserverbeginstosubscribetoit.Thisisdifferentfroma"hot"Observable,whichwillemititemseveniftherearenoobserverssubscribedtoit.Sincethismeansthateventsthatoccurbeforeanobserverisattachedarelost,HTTPObservablesdemandacolddesignation.

TheonNextmethodistermed"emission"sincethereisassociateddatathatisbeingemitted.TheonCompletedandonErrormethodsaretermed"notifications,"astheyrepresentsomethingofsignificance,buttheydonothaveanassociatedeventthatwouldbeconsideredpartofthestream.

Page 319: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoImplementingaPublish-SubscribemodelusingSubjectsshowsyouhowtoconfigureinputandoutputforRxJSObservablesBuildingafullyfeaturedAutoCompletewithObservablesgivesyouabroadtourofsomeoftheutilitiesofferedtoyouaspartoftheRxJSlibrary

Page 320: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingaPublish-SubscribemodelusingSubjectsAngular2willoftenprovideyouwithanObservableinterfacetoattachtoforfree,butitisimportanttoknowhowtheyarecreated,configured,andused.Morespecifically,itisvaluableforyoutoknowhowtotakeObservablesandapplythemtorealscenariosthatwillbeencounteredintheclient.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4839/.

Page 321: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoustartedwiththefollowingskeletonapplication:

[app/click-observer.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'click-observer',

template:`

<button>

Emitevent!

</button>

<p*ngFor="letclickofclicks;leti=index">

{{i}}:{{click}}

</p>

`

})

exportclassClickObserverComponent{

clicks:Array<Event>=[];

}

Yourgoalistoconvertthissothatallthebuttonclickeventsareloggedintotherepeatedfield.

Page 322: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Accomplishingthiswithacomponentmembermethodandusingitintheclickeventbindinginthetemplateispossible,butthisdoesn'tcapturetherealvalueofObservables.YouwanttobeabletoexposeanObservableonClickObserverComponent.Thiswillallowanyotherpartofyourapplicationtosubscribetotheseclickeventsandhandletheminitsownway.

Instead,youwouldliketobeabletofunneltheclickeventsfromthebuttonintotheObservable.WitharegularObservableinstance,thisisn'tpossiblesinceitisonlyactingastheSubscribepartofthePublish-Subscribemodel.ToaccomplishthePublishaspect,youmustuseaSubjectinstance:

[app/click-observer.component.ts]

import{Component}from'@angular/core';

import{Subject}from'rxjs/Subject';

@Component({

selector:'click-observer',

template:`

<button>

Emitevent!

</button>

<p*ngFor="letclickofclicks;leti=index">

{{i}}:{{click}}

</p>

`

})

exportclassClickObserverComponent{

clickEmitter:Subject<Event>=newSubject();

clicks:Array<Event>=[];

}

ReactiveXSubjectsactasboththeObservableandtheObserver.Therefore,itexposesboththesubscribe()method,usedfortheSubscribebehavior,andthenext()method,usedforthePublishbehavior.

Note

Inthisexample,thenext()methodisusefulbecauseyouwanttoexplicitlyspecifywhenanemissionshouldoccurandwhatthatemissionshouldcontain.TherearelotsofwaysofinstantiatingObservablesinordertoimplicitlygenerateemissions,suchas(butcertainlynotlimitedto)Observable.range().Inthesecases,Observableunderstandshowitsinputbehaves,andthusitdoesnotneeddirectionastowhenemissionsoccurandwhattheyshouldcontain.

Inthiscase,youcanpasstheeventdirectlytonext()inthetemplateclickhandlerdefinition.

Page 323: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Withthis,allthatisleftistopopulatethearraybydirectingtheemissionsintoit:

[app/click-observer.component.ts]

import{Component}from'@angular/core';

import{Subject}from'rxjs/Subject';

@Component({

selector:'click-observer',

template:`

<button(click)="clickEmitter.next($event)">

Emitevent!

</button>

<p*ngFor="letclickofclicks;leti=index">

{{i}}:{{click}}

</p>

`

})

exportclassClickObserverComponent{

clickEmitter:Subject<Event>=newSubject();

clicks:Array<Event>=[];

constructor(){

this.clickEmitter

.subscribe(clickEvent=>this.clicks.push(clickEvent));

}

}

That'sall!Withthis,youshouldseeclickeventspopulateinthebrowserwitheachsuccessivebuttonclick.

Page 324: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...ReactiveXObservablesandObserversaredistinct,buttheirbehaviorismutuallycompatibleinsuchawaythattheirunion,Subject,canactaseitheroneofthem.Inthisexample,theSubjectisusedastheinterfacetofeedinEventobjectsasthePublishmodalityaswellastohandletheresultthatwouldcomeoutastheSubscribemodality.

Page 325: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Thewaythisisconstructedmightfeelabitstrangetoyou.ThecomponentisexposingtheSubjectinstanceasthepointwhereyourapplicationwillattachobserverhandlers.

However,youwanttopreventotherpartsoftheapplicationfromaddingadditionalevents,whichisstillpossibleshouldtheychoosetousethenext()method.What'smore,theSubjectinstanceisreferenceddirectlyinsidethetemplateandexposingittheremayfeelabitodd.Therefore,itisdesirable,andcertainlygoodsoftwarepractice,toonlyexposetheObservablecomponentoftheSubject.

Todothis,youmustimporttheObservablemoduleandutilizetheSubjectinstance'smembermethod,namelyasObservable().ThismethodwillcreateanewObservableinstancethatwilleffectivelypipetheobservedemissionsfromtheSubjectintothenewObservable,whichwillbeexposedasapubliccomponentmember:

[app/article.component.ts]

import{Component}from'@angular/core';

import{Observable}from'rxjs/Observable';

import{Subject}from'rxjs/Subject';

@Component({

selector:'click-observer',

template:`

<button(click)="publish($event)">

Emitevent!

</button>

<p*ngFor="letclickofclicks;leti=index">

{{i}}:{{click}}

</p>

`

})

exportclassClickObserverComponent{

clickEmitter:Observable<Event>;

privateclickSubject_:Subject<Event>=newSubject();

clicks:Array<Event>=[];

constructor(){

this.clickEmitter=this.clickSubject_.asObservable();

this.clickEmitter.subscribe(clickEvent=>

this.clicks.push(clickEvent));

}

publish(e:Event):void{

this.clickSubject_.next(e);

}

}

Page 326: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NoweventhoughonlythiscomponentisreferencingclickEmitter,everycomponentthatusesclickEmitterwillnotneedorbeabletotouchthesource,Subject.

NativeRxJSimplementation

Thishasallbeenagreatexample,butthisissuchacommonpatterninthattheRxJSlibraryalreadyprovidesabuilt-inwayofimplementingit.TheObservableclassexposesastaticmethodfromEvent(),whichtakesinanelementthatisexpectedtogenerateeventsandtheeventtypetolistento.

However,youneedareferencetotheactualelement,whichyoucurrentlydonothave.Forthepresentimplementation,theAngular2ViewChildfacultieswillgiveyouaverynicereferencetothebutton,whichwillthenbepassedtothefromEvent()methodoncethetemplatehasbeenrendered:

[app/click-observer.component.ts]

import{Component,ViewChild,ngAfterViewInit}

from'@angular/core';

import{Observable}from'rxjs/Observable';

import'rxjs/add/observable/fromEvent';

@Component({

selector:'click-observer',

template:`

<button#btn>

Emitevent!

</button>

<p*ngFor="letclickofclicks;leti=index">

{{i}}:{{click}}

</p>

`

})

exportclassClickObserverComponentimplementsAfterViewInit{

@ViewChild('btn')btn;

clickEmitter:Observable<Event>;

clicks:Array<Event>=[];

ngAfterViewInit(){

this.clickEmitter=Observable.fromEvent(

this.btn.nativeElement,'click');

this.clickEmitter.subscribe(clickEvent=>

this.clicks.push(clickEvent));

}

}

Withallofthis,thecomponentshouldstillbehaveidentically.

Page 327: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBasicutilizationofObservableswithHTTPdemonstratesthebasicsofhowtouseanobservableinterfaceCreatinganObservableauthenticationserviceusingBehaviorSubjectsinstructsyouonhowtoreactivelymanagethestateinyourapplicationBuildingafullyfeaturedAutoCompletewithObservablesgivesyouabroadtourofsomeoftheutilitiesofferedtoyouaspartoftheRxJSlibrary

Page 328: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CreatinganObservableauthenticationserviceusingBehaviorSubjectsOneofthemostobviousandusefulcasesoftheObserverPatternistheoneinwhichasingleentityinyourapplicationunidirectionallycommunicatesinformationtoafieldoflistenersontheoutside.Theselistenerswouldliketobeabletoattachanddetachfreelyfromthesinglebroadcastingentity.Agoodinitialexampleofthisisthelogin/logoutcomponent.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6957/.

Page 329: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhavethefollowingskeletonapplication:

[app/login.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'login',

template:`

<button*ngIf="!loggedIn"

(click)="loggedIn=true">

Login

</button>

<button*ngIf="loggedIn"

(click)="loggedIn=false">

Logout

</button>

`

})

exportclassLoginComponent{

loggedIn:boolean=false;

}

Asitpresentlyexists,thiscomponentwillallowyoutotogglebetweenthelogin/logoutbutton,butthereisnoconceptofsharedapplicationstate,andothercomponentscannotutilizetheloginstatethatthiscomponentwouldtrack.

YouwouldliketointroducethisstatetoasharedservicethatisoperatedusingtheObserverPattern.

Page 330: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbycreatinganemptyserviceandinjectingitintothiscomponent:

[app/authentication.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassAuthService{

privateauthState_:AuthState;

}

exportconstenumAuthState{

LoggedIn,

LoggedOut

}

NoticethatyouareusingaTypeScriptconstenumtokeeptrackoftheuser'sauthenticationstate.

Note

Ifyou'renewtoES6andTypeScript,thesekeywordsmayfeelabitbizarretoyou.TheconstkeywordisfromtheES6specification,signifyingthatthisvalueisreadonlyoncedeclared.InvanillaES6,thiswillthrowanerror,usuallySyntaxError,atruntime.WithTypeScriptcompilationthough,constwillbecaughtatcompiletime.

TheenumkeywordisanofferingofTypeScript.Itisnotdissimilartoaregularobjectliteral,butnotethattheenummembersdonothavevalues.

Throughouttheapplication,youwillreferencetheseviaAuthState.LoggedInandAuthState.LoggedOut.IfyoureferencethecompiledJavaScriptthatTypeScriptgenerates,youwillseethattheseareactuallyassignedintegervalues.Butforthepurposesofbuildinglargeapplications,thisallowsustodevelopacentralizedrepositoryofpossibleAuthStatevalueswithoutworryingabouttheiractualvalues.

Injectingtheauthenticationservice

Astheskeletonservicecurrentlyexists,youaregoingtoinstantiateaSubjectthatwillemitAuthState,butthereisnowayavailablecurrentlytointeractwithit.Youwillsetthisupinabit.First,youmustinjectthisserviceintoyourcomponent:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{LoginComponent}from'./login.component';

import{AuthService}from'./authentication.service';

Page 331: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

@NgModule({

imports:[

BrowserModule

],

declarations:[

LoginComponent

],

providers:[

AuthService

],

bootstrap:[

LoginComponent

]

})

exportclassAppModule{}

Thisisallwellandgood,buttheserviceisstillunusableasis.

Tip

NotethatthepathyouimportyourAuthServicefrommayvarydependingonwhereitliesinyourfiletree.

AddingBehaviorSubjecttotheauthenticationservice

Thecoreofthisserviceistomaintainaglobalapplicationstate.Itshouldexposeitselftotherestoftheapplicationbylettingotherpartssaytotheservice,"Letmeknowwheneverthestatechanges.Also,I'dliketoknowwhatthestateisrightnow."TheperfecttoolforthistaskisBehaviorSubject.

Note

RxJSSubjectsalsohaveseveralsubclasses,andBehaviorSubjectisoneofthem.Fundamentally,itfollowsalltherhythmsofSubjects,butthemaindifferenceisthatitwillemititscurrentstatetoanyobserverthatbeginstolistentoit,asifthateventisentirelynew.Incaseslikethis,whereyouwanttokeeptrackofthestate,thisisextremelyuseful.

AddaprivateBehaviorSubject(initializedtotheLoggedOutstate)andapublicObservabletoAuthService:

[app/authentication.service.ts]

import{Injectable}from'@angular/core';

import{BehaviorSubject}from'rxjs/BehaviorSubject';

import{Observable}from'rxjs/Observable';

@Injectable()

exportclassAuthService{

privateauthManager_:BehaviorSubject<AuthState>

Page 332: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

=newBehaviorSubject(AuthState.LoggedOut);

privateauthState_:AuthState;

authChange:Observable<AuthState>;

constructor(){

this.authChange=this.authManager_.asObservable();

}

}

exportconstenumAuthState{

LoggedIn,

LoggedOut

}

AddingAPImethodstotheauthenticationservice

RecallthatyoudonotwanttoexposetheBehaviorSubjectinstancetooutsideactors.Instead,youwouldliketoofferonlyitsObservablecomponent,whichyoucanopenlysubscribeto.Furthermore,youwouldliketoallowoutsideactorstosettheauthenticationstate,butonlyindirectly.Thiscanbeaccomplishedwiththefollowingmethods:

[app/authentication.service.ts]

import{Injectable}from'@angular/core';

import{BehaviorSubject}from'rxjs/BehaviorSubject';

import{Observable}from'rxjs/Observable';

@Injectable()

exportclassAuthService{

privateauthManager_:BehaviorSubject<AuthState>

=newBehaviorSubject(AuthState.LoggedOut);

privateauthState_:AuthState;

authChange:Observable<AuthState>;

constructor(){

this.authChange=this.authManager_.asObservable();

}

login():void{

this.setAuthState_(AuthState.LoggedIn);

}

logout():void{

this.setAuthState_(AuthState.LoggedOut);

}

emitAuthState():void{

this.authManager_.next(this.authState_);

}

privatesetAuthState_(newAuthState:AuthState):void{

this.authState_=newAuthState;

this.emitAuthState();

}

}

exportconstenumAuthState{

Page 333: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

LoggedIn,

LoggedOut

}

Outstanding!Withallofthis,outsideactorswillbeabletosubscribetoauthChangeObservableandwillindirectlycontrolthestatevialogin()andlogout().

Tip

NotethattheObservablecomponentofBehaviorSubjectisnamedauthChange.NamingthedifferentcomponentsoftheelementsintheObserverPatterncanbetricky.ThisnamingconventionwasselectedtorepresentwhataneventemittedfromtheObservableactuallymeant.Quiteliterally,authChangeistheanswertothequestion,"WhateventamIobserving?".Therefore,itmakesgoodsemanticsensethatyourcomponentsubscribestoauthChangeswhentheauthenticationstatechanges.

Wiringtheservicemethodsintothecomponent

LoginComponentdoesnotyetutilizetheservice,soaddinitsnewlycreatedmethods:

[app/login.component.ts]

import{Component}from'@angular/core';

import{AuthService,AuthState}from'./authentication.service';

@Component({

selector:'login',

template:`

<button*ngIf="!loggedIn"

(click)="login()">

Login

</button>

<button*ngIf="loggedIn"

(click)="logout()">

Logout

</button>

`

})

exportclassLoginComponent{

loggedIn:boolean;

constructor(privateauthService_:AuthService){

authService_.authChange.subscribe(

newAuthState=>

this.loggedIn=(newAuthState===AuthState.LoggedIn));

}

login():void{

this.authService_.login();

}

Page 334: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

logout():void{

this.authService_.logout();

}

}

Withallofthisinplace,youshouldbeabletoseeyourlogin/logoutbuttonsfunctionwell.ThismeansyouhavecorrectlyincorporatedObservableintoyourcomponent.

Tip

Thisrecipeisagoodexampleofconventionsyou'rerequiredtomaintainwhenusingpublic/private.Notethattheinjectedserviceisdeclaredasaprivatememberandwrappedwithpubliccomponentmembermethods.Anythingthatanotherpartoftheapplicationcallsoranythingthatisusedinsidethetemplateshouldbeapublicmember.

Page 335: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...CentraltothisimplementationisthateachcomponentthatislisteningtoObservablehasanidempotenthandlingofeventsthatareemitted.EachtimeanewcomponentisconnectedtoObservable,itinstructstheservicetoemitwhateverthecurrentstateis,usingemitAuthState().Necessarily,allcomponentsdon'tbehaveanydifferentlyiftheyseethesamestateemittedmultipletimesinarow;theywillonlyaltertheirbehavioriftheyseeachangeinthestate.

Noticehowyouhavetotallyencapsulatedtheauthenticationstateinsidetheauthenticationservice,andatthesametime,haveexposedandutilizedareactiveAPIfortheentireapplicationtobuildupon.

Page 336: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Twocriticalcomponentsofhookingintoservicessuchasthesearethesetupandteardownprocesses.AfastidiousdeveloperwillhavenoticedthatevenifaninstanceofLoginComponentisdestroyed,thesubscriptiontoObservablewillstillpersist.This,ofcourse,isextremelyundesirable!

Fortunately,thesubscribe()methodofObservablesreturnsaninstanceofSubscription,whichexposesanunsubscribe()method.Youcanthereforecapturethisinstanceupontheinvocationofsubscribe()andtheninvokeitwhenthecomponentisbeingtorndown.

SimilartolistenerteardowninAngular1,youmustinvoketheunsubscribemethodwhenthecomponentinstanceisbeingdestroyed.Happily,theAngular2lifecycleprovidesyouwithsuchamethod,ngOnDestroy,inwhichyoucaninvokeunsubscribe():

[app/login.component.ts]

import{Component,ngOnDestroy}from'@angular/core';

import{AuthService,AuthState}from'./authentication.service';

import{Subscription}from'rxjs/Subscription';

@Component({

selector:'login',

template:`

<button*ngIf="!loggedIn"

(click)="login()">

Login

</button>

<button*ngIf="loggedIn"

(click)="logout()">

Logout

</button>

`

})

exportclassLoginComponentimplementsOnDestroy{

loggedIn:boolean;

privateauthChangeSubscription_:Subscription;

constructor(privateauthService_:AuthService){

this.authChangeSubscription_=

authService_.authChange.subscribe(

newAuthState=>

this.loggedIn=(newAuthState===AuthState.LoggedIn));

}

login():void{

this.authService_.login();

}

logout():void{

Page 337: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

this.authService_.logout();

}

ngOnDestroy(){

this.authChangeSubscription_.unsubscribe();

}

}

Nowyourapplicationissafefrommemoryleaksshouldanyinstanceofthiscomponenteverbedestroyedinthelifetimeofyourapplication.

Page 338: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBasicutilizationofObservableswithHTTPdemonstratesthebasicsofhowtouseanobservableinterfaceImplementingaPublish-SubscribemodelusingSubjectsshowsyouhowtoconfigureinputandoutputforRxJSObservablesBuildingageneralizedPublish-Subscribeservicetoreplace$broadcast,$emit,and$onassemblesarobustPubSubmodelforconnectingapplicationcomponentswithchannelsBuildingafullyfeaturedAutoCompletewithObservablesgivesyouabroadtourofsomeoftheutilitiesofferedtoyouaspartoftheRxJSlibrary

Page 339: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BuildingageneralizedPublish-Subscribeservicetoreplace$broadcast,$emit,and$onInAngular1,the$emitand$broadcastbehaviorswereindeedveryusefultools.Theygaveyoutheabilitytosendcustomeventsupwardsanddownwardsthroughthescopetreetoanylistenersthatmightbewaitingforsuchanevent.Thispushedthedevelopertowardsaveryusefulpattern:theabilityformanycomponentstobeabletotransmiteventstoandfromacentralsource.However,using$emitand$broadcastforsuchapurposewasgrosslyinappropriate;theyhadtheeffectoffeedingtheeventthroughhugenumbersofscopesonlytoreachthesingleintendedtarget.

Note

Inthepreviouseditionofthisbook,thecorrespondingrecipedemonstratedhowtobuildaPublish-Subscribeservicethatusedthe$emitand$rootScopeinjection.Theversioninthisrecipe,althoughdifferentinahandfulofways,achievessimilarresultsinasubstantiallycleanerandmoreelegantfashion.

Itispreferabletocreateasingleentitythatcanserveasagenericthroughwayforeventstopassfrompublisherstotheirsubscribers.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/2417/.

Page 340: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwithaskeletonserviceinjectedintoacomponent:

[app/node.component.ts]

import{Component,Input}from'@angular/core';

import{PubSubService}from'./publish-subscribe.service';

@Component({

selector:'node',

template:`

<p>Heard{{count}}of{{subscribeChannel}}</p>

<button(click)="send()">Send{{publishChannel}}</button>

`

})

exportclassNodeComponent{

@Input()publishChannel:string;

@Input()subscribeChannel:string;

count:number=0;

constructor(privatepubSubService_:PubSubService){}

send(){}

}

[app/publish-subscribe.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassPubSubService{

constructor(){}

publish(){}

subscribe(){}

}

Page 341: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Thegroundworkforthisimplementationshouldbeprettyobvious.TheserviceisgoingtohostasingleSubjectinstancethatisgoingtofunneleventsofanytypeintotheserviceandoutthroughtheobserversoftheSubject.

First,implementthefollowingsothatsubscribe()andpublish()actuallydoworkwhenyouinvolvetheSubjectinstance:

[app/publish-subscribe.service.ts]

import{Injectable}from'@angular/core';

import{Subject}from'rxjs/Subject';

import{Observable}from'rxjs/Observable';

import{Observer}from'rxjs/Observer';

import{Subscriber}from'rxjs/Subscriber;

@Injectable()

exportclassPubSubService{

privatepublishSubscribeSubject_:Subject<any>=newSubject();

emitter_:Observable<any>;

constructor(){

this.emitter_=this.publishSubscribeSubject_.asObservable();

}

publish(event:any):void{

this.publishSubscribeSubject_.next(event);

}

subscribe(handler:NextObserver<any>):Subscriber{

returnthis.emitter_.subscribe(handler);

}

}

Thisisterrificforaninitialimplementation,butyieldsaproblem:everyeventpublishedtothisservicewillbebroadcastedtoallthesubscribers.

Introducingchannelabstraction

Itispossibleandinfactquiteeasytorestrictpublishandsubscribeinsuchawaythattheywillonlypayattentiontothechanneltheyspecify.First,modifypublish()tonesttheeventinsidetheemittedobject:

[app/publish-subscribe.service.ts]

import{Injectable}from'@angular/core';

import{Subject}from'rxjs/Subject';

import{Observable}from'rxjs/Observable';

import{Observer}from'rxjs/Observer';

Page 342: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Subscriber}from'rxjs/Subscriber;

@Injectable()

exportclassPubSubService{

privatepublishSubscribeSubject_:Subject<any>=newSubject();

emitter_:Observable<any>;

constructor(){

this.emitter_=this.publishSubscribeSubject_.asObservable();

}

publish(channel:string,event:any):void{

this.publishSubscribeSubject_.next({

channel:channel,

event:event

});

}

subscribe(handler:NextObserver<any>):Subscriber{

returnthis.emitter_.subscribe(handler);

}

}

Withthis,youarenowabletoutilizesomeObservablebehaviortorestrictwhicheventsthesubscriptionispayingattentionto.

Observableemissionscanhavefilter()andmap()appliedtothem.filter()willreturnanewObservableinstancethatonlyemitswhicheveremissionsevaluateastrueinitsfilterfunction.map()returnsanewObservableinstancethattransformsallemissionsintoanewvalue.

[app/publish-subscribe.service.ts]

import{Injectable}from'@angular/core';

import{Subject}from'rxjs/Subject';

import{Observable}from'rxjs/Observable';

import{Observer}from'rxjs/Observer';

import{Subscriber}from'rxjs/Subscriber;

import'rxjs/add/operator/filter';

import'rxjs/add/operator/map';

@Injectable()

exportclassPubSubService{

privatepublishSubscribeSubject_:Subject<any>=newSubject();

emitter_:Observable<any>;

constructor(){

this.emitter_=this.publishSubscribeSubject_.asObservable();

}

publish(channel:string,event:any):void{

this.publishSubscribeSubject_.next({

channel:channel,

Page 343: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

event:event

});

}

subscribe(channel:string,handler:((value:any)=>void)):Subscriber{

returnthis.emitter_

.filter(emission=>emission.channel===channel)

.map(emission=>emission.event)

.subscribe(handler);

}

}

Hookingcomponentsintotheservice

Theserviceiscomplete,butthecomponentdoesn'tyethavetheabilitytouseit.Usetheinjectedservicetolinkthecomponenttothechannelsspecifiedbyitsinputstrings:

[app/node.component.ts]

import{Component,Input}from'@angular/core';

import{PubSubService}from'./publish-subscribe.service';

@Component({

selector:'node',

template:`

<p>Heard{{count}}of{{subscribeChannel}}</p>

<button(click)="send()">Send{{publishChannel}}</button>

`

})

exportclassNodeComponent{

@Input()publishChannel:string;

@Input()subscribeChannel:string;

count:number=0;

constructor(privatepubSubService_:PubSubService){}

send(){

this.pubSubService_

.publish(this.publishChannel,{});

}

ngAfterViewInit(){

this.pubSubService_

.subscribe(this.subscribeChannel,

event=>++this.count);

}

}

Tip

Thepublish()methodhasanemptyobjectliteralasitssecondargument.Thisisthepayloadforthepublishedmessage,whichisn'tusedinthisrecipe.Ifyouwanttosenddataalongwitha

Page 344: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

message,thisiswhereitwouldgo.

Withallofthis,testyourapplicationwiththefollowing:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<nodesubscribeChannel="foo"

publishChannel="bar">

</node>

<nodesubscribeChannel="bar"

publishChannel="foo">

</node>

`

})

exportclassRootComponent{}

Youwillseethatchannelpublishingandsubscribingishappeningasyouwouldexpect.

Unsubscribingfromchannels

Ofcourse,youwanttoavoidmemoryleakswhereverpossible.Thisrequiresthatyouexplicitlycompletethecleanupprocesswhenyourcomponentinstanceisdestroyed:

[app/node.component.ts]

import{Component,Input,OnDestroy}from'@angular/core';

import{PubSubService}from'./publish-subscribe.service';

import{Subscription}from'rxjs/Subscription';

@Component({

selector:'node',

template:`

<p>Heard{{count}}of{{subscribeChannel}}</p>

<button(click)="send()">Send{{publishChannel}}</button>

`

})

exportclassNodeComponentimplementsOnDestroy{

@Input()publishChannel:string;

@Input()subscribeChannel:string;

count:number=0;

privatepubSubServiceSubscription_:Subscription;

constructor(privatepubSubService_:PubSubService){}

send(){

this.pubSubService_

.publish(this.publishChannel,{});

Page 345: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

ngAfterViewInit(){

this.pubSubService_

.subscribe(this.subscribeChannel,

event=>++this.count);

}

ngOnDestroy(){

this.pubSubServiceSubscription_.unsubscribe();

}

}

Page 346: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Eachtimepublish()isinvoked,theprovidedeventiswrappedbytheprovidedchannelandsubmittedtoacentralSubject,whichisprivateinsidetheservice.Atthesametime,thefactthateachinvocationofsubscribe()wantstolistentoadifferentchannelpresentsaproblem.ThisisbecauseanObservabledoesnotdrawdistinctionsregardingwhatisbeingemittedwithoutexplicitdirection.

Youareabletoutilizethefilter()andmap()operatorstoestablishacustomizedviewoftheemissionsofSubjectandusethisviewintheapplicationoftheObserverhandler.Eachtimesubscribe()isinvoked,itcreatesanewObservableinstance;however,theseareallmerelypointsofindirectionfromtheonetrueObservable,whichisownedbytheprivateinstancehiddeninsidetheservice.

Page 347: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...It'simportanttounderstandwhythisserviceisnotbuiltinadifferentway.

AnimportantfeatureofObservablesistheirabilitytobecomposed.Thatis,severalObservableinstancesindependentlyemittingeventscanbecombinedintooneObservableinstance,whichwillemitalltheeventsfromacombinedsource.Thiscanbeaccomplishedinseveraldifferentways,includingflatMap()ormerge().ThisabilityiswhatisbeingreferredtowhenReactiveXObservablesaredescribedas"composable."

Therefore,adevelopermightseethiscompositionabilityandthinkitwouldbesuitableforaPublish-Subscribeentity.TheentitywouldacceptObservableinstancesfromthepublishers.TheywouldbecombinedtocreateasingleObservableinstance,andsubscriberswouldattachObservabletothiscombination.Whatcouldpossiblygowrong?

ConsiderationsofanObservable'scompositionandmanipulation

OneprimaryconcernisthatthecomposedObservablethatthesubscribersarebeingattachedtowillchangeconstantly.Asisthecasewithmap()andfilter(),anymodulationperformedonanObservableinstance,includingcomposition,willreturnanewObservableinstance.ThisnewinstancewouldbecometheObservablethatsubscriberswouldattachto,andthereinliestheproblem.

Let'sexaminethisproblemstepbystep:

1. PubSubserviceemitseventsfromObservableA.2. NodeXsubscribestotheserviceandreceiveseventsfromObservableA.3. SomeotherpartoftheapplicationaddsObservableBtothePubSubservice.4. ThePubSubservicecomposesObservableAandObservableBintoObservableAB.5. NodeYsubscribestotheserviceandreceiveseventsfromObservableAB.

Notethatinthiscase,NodeXwouldstillreceiveeventsfromonlyObservableAsincethatistheObservableinstancewhereitinvokedsubscribe().

Certainly,therearestepsthatcanbetakentomitigatethisproblem,suchashavinganadditionallevelofindirectionbetweenthesubscribeObservableandthecomposedObservable.However,awiseengineerwillstepbackatthispointandtakestockofthesituation.Publish-Subscribeissupposedtobearelatively"dumb"protocol,meaningthatitshouldn'tbedelegatedtoomuchresponsibilityaroundmanagingtheeventsithasbeenpassedwith—messagesinandmessagesout,withnorealconcernforwhatiscontainedaslongastheygetthere.OnecouldmakeaverystrongargumentthatintroducingObservablesinthePublishsidegreatlyovercomplicatesthings.

Page 348: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Inthecaseofthisrecipe,youhavedevelopedanelegantandsimpleversionofaPublish-Subscribemodule,anditfeelsrighttodelegatecomplexityoutsideofit.InthecaseofentitieswantingtousePublishwithObservables,asolutionmightbetojustpipetheObservableemissionsintotheservice'spublish()method.

Page 349: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBasicutilizationofObservableswithHTTPdemonstratesthebasicsofhowtouseanobservableinterfaceImplementingaPublish-SubscribemodelusingSubjectsshowsyouhowtoconfigureinputandoutputforRxJSObservablesCreatinganObservableauthenticationserviceusingBehaviorSubjectsinstructsyouonhowtoreactivelymanagethestateinyourapplicationBuildingafullyfeaturedAutoCompletewithObservablesgivesyouabroadtourofsomeoftheutilitiesofferedtoyouaspartoftheRxJSlibrary

Page 350: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UsingQueryListsandObservablestofollowchangesinViewChildrenOneveryusefulpieceofbehaviorincomponentsistheabilitytotrackchangestothecollectionsofchildrenintheview.Inmanyways,thisisquiteanebuloussubject,asthenumberofwaysinwhichviewcollectionscanbealteredisnumerousandsubtle.Thankfully,Angular2providesasolidfoundationfortrackingthesechanges.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4112/.

Page 351: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeginwiththefollowingskeletonapplication:

[app/inner.component.ts]

import{Component,Input}from'@angular/core';

@Component({

selector:'inner',

template:`<p>{{val}}`

})

exportclassInnerComponent{

@Input()val:number;

}

[app/outer.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'outer',

template:`

<button(click)="add()">Moar</button>

<button(click)="remove()">Less</button>

<button(click)="shuffle()">Shuffle</button>

<inner*ngFor="letioflist"

val="{{i}}">

</inner>

`

})

exportclassOuterComponent{

list:Array<number>=[];

add():void{

this.list.push(this.list.length)

}

remove():void{

this.list.pop();

}

shuffle():void{

//simpleassignmentshuffle

this.list=this.list.sort(()=>(4*Math.random()>2)?1:-1);

}

}

Asis,thisisaverysimplelistmanagerthatgivesyoutheabilitytoadd,remove,andshufflealistinterpolatedasInnerComponentinstances.Youwanttheabilitytotrackwhenthislistundergoeschangesandkeepreferencestothecomponentinstancesthatcorrespondtotheviewcollection.

Page 352: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...BeginbyusingViewChildrentocollecttheInnerComponentinstancesintoasingleQueryList:

[app/outer.component.ts]

import{Component,ViewChildren,QueryList}from'@angular/core';

import{InnerComponent}from'./inner.component';

@Component({

selector:'outer',

template:`

<button(click)="add()">Moar</button>

<button(click)="remove()">Less</button>

<button(click)="shuffle()">Shuffle</button>

<inner*ngFor="letioflist"

val="{{i}}">

</inner>

`

})

exportclassOuterComponent{

@ViewChildren(InnerComponent)innerComponents:

QueryList<InnerComponent>;

list:Array<number>=[];

add():void{

this.list.push(this.list.length)

}

remove():void{

this.list.pop();

}

shuffle():void{

//simpleassignmentshuffle

this.list=this.list.sort(()=>(4*Math.random()>2)?1:-1);

}

}

Easy!Now,oncetheviewofOuterComponentisinitialized,youwillbeabletousethis.innerComponentstoreferenceQueryList.

DealingwithQueryLists

QueryListsarestrangebirdsinAngular2,butlikemanyotherfacetsoftheframework,theyarejustaconventionthatyouwillhavetolearn.Inthiscase,theyareanimmutableanditerablecollectionthatexposesahandfulofmethodstoinspectwhattheycontainandwhenthesecontentsarealtered.

Inthiscase,thetwoinstancepropertiesyoucareaboutarelastandchanges.last,asyoumight

Page 353: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

expect,willreturnthelastinstanceofQueryList—inthiscase,aninstanceofInnerComponentifQueryListisnotempty.changeswillreturnanObservablethatwillemitQueryListwheneverachangeoccursinsideit.InthecaseofacollectionofInnerComponentinstances,theaddition,removal,andshufflingoptionswillallberegisteredaschanges.

Usingtheseproperties,youcanveryeasilysetupOuterComponenttokeeptrackofwhatthevalueofthelastInnerComponentinstanceis:

import{Component,ViewChildren,QueryList}from'@angular/core';

import{InnerComponent}from'./inner.component';

@Component({

selector:'app-outer',

template:`

<button(click)="add()">Moar</button>

<button(click)="remove()">Less</button>

<button(click)="shuffle()">Shuffle</button>

<app-inner*ngFor="letioflist"

val="{{i}}">

</app-inner>

<p>Valueoflast:{{lastVal}}</p>

`

})

exportclassOuterComponent{

@ViewChildren(InnerComponent)innerComponents:

QueryList<InnerComponent>;

list:Array<number>=[];

lastVal:number;

constructor(){}

add(){

this.list.push(this.list.length)

}

remove(){

this.list.pop();

}

shuffle(){

this.list=this.list.sort(()=>(4*Math.random()>2)?1:-1);

}

ngAfterViewInit(){

this.innerComponents.changes

.subscribe(e=>this.lastVal=(e.last||{}).val);

}

}

Withallofthis,youshouldbeabletofindthatlastValwillstayuptodatewithanychangesyouwouldtriggerintheInnerComponentcollection.

Page 354: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Correctingtheexpressionchangederror

Ifyouruntheapplicationasis,youwillnoticethatanerroristhrownafteryouclickontheMoarbuttonthefirsttime:

Expressionhaschangedafteritwaschecked

ThisisanerroryouwillmostlikelyseefrequentlyinAngular2.Themeaningissimple:sinceyouare,bydefault,operatingindevelopmentmode,Angularwillchecktwicetoseethatanyboundvaluesdonotchangeafterallofthechangedetectionlogichasbeenresolved.Inthecaseofthisrecipe,theemissionbyQueryListmodifieslastVal,whichAngulardoesnotexpect.Thus,you'llneedtoexplicitlyinformtheframeworkthatthevalueisexpectedtochangeagain.ThiscanbeaccomplishedbyinjectingChangeDetectorRef,whichallowsyoutotriggerachangedetectioncycleoncethevalueischanged:

import{Component,ViewChildren,QueryList,ngAfterViewInit,

ChangeDetectorRef}from'@angular/core';

import{InnerComponent}from'./inner.component';

@Component({

selector:'outer',

template:`

<button(click)="add()">Moar</button>

<button(click)="remove()">Less</button>

<button(click)="shuffle()">Shuffle</button>

<inner*ngFor="letioflist"

val="{{i}}">

</inner>

<p>Valueoflast:{{lastVal}}</p>

`

})

exportclassOuterComponentimplementsAfterViewInit{

@ViewChildren(InnerComponent)innerComponents:

QueryList<InnerComponent>;

list:Array<number>=[];

lastVal:number;

constructor(privatechangeDetectorRef_:ChangeDetectorRef){}

add():void{

this.list.push(this.list.length)

}

remove():void{

this.list.pop();

}

shuffle():void{

//simpleassignmentshuffle

this.list=this.list.sort(()=>(4*Math.random()>2)?1:-1);

}

Page 355: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ngAfterViewInit(){

this.innerComponents.changes

.subscribe(innerComponents=>{

this.lastVal=(innerComponents.last||{}).val;

this.changeDetectorRef_.detectChanges();

});

}

}

Atthispoint,everythingshouldworkcorrectlywithnoerrors.

Page 356: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...OncetheOuterComponentviewisinitialized,youwillbeabletointeractwithQueryListthatisobtainedusingViewChildren.EachtimethecollectionthatQueryListwrapsismodified,theObservableexposedbyitschangespropertywillemitQueryList,signalingthatsomethinghaschanged.

Hatetheplayer,notthegame

Importantly,Observable<QueryList>doesnottrackchangesinthearrayofnumbers.IttracksthegeneratedcollectionofInnerComponents.ThengForstructuraldirectiveisresponsibleforgeneratingthelistofInnerComponentinstancesintheview.ItisthiscollectionthatQueryListisconcernedwith,nottheoriginalarray.

Thisisagoodthing!ViewChildrenshouldonlybeconcernedwiththecomponentsastheyhavebeenrenderedinsidetheview,notthedatathatcausedthemtoberenderedinsuchafashion.

Oneimportantconsiderationofthisisthatuponeachemission,itisentirelypossiblethatQueryListwillbeempty.Asshownabove,sincetheObserveroftheQueryList.changesObservabletriestoreferenceapropertyoflast,itisnecessarytohaveafallbackobjectliteralintheeventthatlastreturnsundefined.

Page 357: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBasicUtilizationofObservableswithHTTPdemonstratesthebasicsofhowtouseanobservableinterfaceBuildingafullyfeaturedAutoCompletewithObservablesgivesyouabroadtourofsomeoftheutilitiesofferedtoyouaspartoftheRxJSlibrary

Page 358: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BuildingafullyfeaturedAutoCompletewithObservablesRxJSObservablesaffordyoualotoffirepower,anditwouldbeashametomissoutonthem.Ahugelibraryoftransformationsandutilitiesarebakedrightinthatallowyoutoelegantlyarchitectcomplexportionsofyourapplicationinareactivefashion.

Inthisrecipe,you'lltakeanaïveautocompleteformandbuildarobustsetoffeaturestoenhancebehaviorandperformance.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/8629/.

Page 359: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththefollowingapplication:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{SearchComponent}from'./search.component';

import{APIService}from'./api.service';

import{HttpModule}from'@angular/http';

@NgModule({

imports:[

BrowserModule,

HttpModule

],

declarations:[

SearchComponent

],

providers:[

APIService

],

bootstrap:[

SearchComponent

]

})

exportclassAppModule{}

[app/search.component.ts]

import{Component}from'@angular/core';

import{APIService}from'./api.service';

@Component({

selector:'search',

template:`

<input#queryField(keyup)="search(queryField.value)">

<p*ngFor="letresultofresults">{{result}}</p>

`

})

exportclassSearchComponent{

results:Array<string>=[];

constructor(privateapiService_:APIService){}

search(query:string):void{

this.apiService_

.search(query)

.subscribe(result=>this.results.push(result));

}

}

Page 360: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/api.service.ts]

import{Injectable}from'@angular/core';

import{Http}from'@angular/http';

import{Observable}from'rxjs/Rx';

@Injectable()

exportclassAPIService{

constructor(privatehttp_:Http){}

search(query:string):Observable<string>{

returnthis.http_

.get('static/response.json')

.map(r=>r.json()['prefix']+query)

//Belowisjustacleverwayofrandomly

//delayingtheresponsebetween0to1000ms

.concatMap(

x=>Observable.of(x).delay(Math.random()*1000));

}

}

YourobjectiveistodramaticallyenhancethisusingRxJS.

Page 361: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Asis,thisapplicationislisteningforkeyupeventsinthesearchinput,performinganHTTPrequesttoastaticJSONfileandaddingtheresponsetoalistofresults.

UsingtheFormControlvalueChangesObservable

Angular2hasobservablebehavioralreadyavailabletoyouinanumberofplaces.OneofthemisinsideReactiveFormsModule,whichallowsyoutouseanObservablethatisattachedtoaforminput.ConvertthisinputtouseFormControl,whichexposesavalueChangesObservable:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{SearchComponent}from'./search.component';

import{APIService}from'./api.service';

import{HttpModule}from'@angular/http';

import{ReactiveFormsModule}from'@angular/forms';

@NgModule({

imports:[

BrowserModule,

HttpModule,

ReactiveFormsModule

],

declarations:[

SearchComponent

],

providers:[

APIService

],

bootstrap:[

SearchComponent

]

})

exportclassAppModule{}

[app/search.component.ts]

import{Component}from'@angular/core';

import{APIService}from'./api.service';

import{FormControl}from'@angular/forms';

@Component({

selector:'search',

template:`

<input[formControl]="queryField">

<p*ngFor="letresultofresults">{{result}}</p>

`

})

Page 362: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassSearchComponent{

results:Array<string>=[];

queryField:FormControl=newFormControl();

constructor(privateapiService_:APIService){

this.queryField.valueChanges

.subscribe(query=>this.apiService_

.search(query)

.subscribe(result=>this.results.push(result)));

}

}

Debouncingtheinput

Eachtimetheinputvaluechanges,Angularwilldutifullyfireoffarequestandhandletheresponseassoonasitisready.Inthecasewheretheuserisqueryingaverylongterm,suchassupercalifragilisticexpialidocious,itmaybenecessaryforyoutoonlysendoffasinglerequestonceyouthinkthey'redonewithtyping,asopposedto34requests,oneforeachtimetheinputchanges.

RxJSObservableshavethisbuiltin.debounceTime(delay)willcreateanewObservablethatwillonlypassalongthelatestvaluewhentherehaven'tbeenanyothervaluesfor<delay>ms.ThisshouldbeaddedtothevalueChangesObservablesincethisisthesourcethatyouwishtodebounce.200mswillbesuitableforyourpurposes:

[app/search.component.ts]

import{Component}from'@angular/core';

import{APIService}from'./api.service';

import{FormControl}from'@angular/forms';

@Component({

selector:'search',

template:`

<input[formControl]="queryField">

<p*ngFor="letresultofresults">{{result}}</p>

`

})

exportclassSearchComponent{

results:Array<string>=[];

queryField:FormControl=newFormControl();

constructor(privateapiService_:APIService){

this.queryField.valueChanges

.debounceTime(200)

.subscribe(query=>this.apiService_

.search(query)

.subscribe(result=>this.results.push(result)));

}

}

Page 363: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Note

Theoriginofthetermdebouncecomesfromtheworldofcircuits.Mechanicalbuttonsorswitchesutilizemetalcontactstoopenandclosecircuitconnections.Whenthemetalcontactsareclosed,theywillbangtogetherandreboundbeforebeingsettled,causingbounce.Thisbounceisproblematicinthecircuit,asitwilloftenregisterasarepeattogglingoftheswitchorbutton—obviouslybuggybehavior.Theworkaroundforthisistofindawaytoignoretheexpectedbouncenoise—debouncing!Thiscanbeaccomplishedbyeitherignoringthebouncenoiseorintroducingadelaybeforereadingthevalue,bothofwhichcanbedonewithhardwareorsoftware.

Ignoringserialduplicates

Sinceyouarereadinginputfromatextbox,itisverypossiblethattheuserwilltypeonecharacter,thentypeanothercharacterandpressbackspace.FromtheperspectiveoftheObservable,sinceitisnowdebouncedbyadelayperiod,itisentirelypossiblethattheuserinputwillbeinterpretedinsuchawaythatthedebouncedoutputwillemittwoidenticalvaluessequentially.RxJSoffersexcellentprotectionagainstthis,distinctUntilChanged(),whichwilldiscardanemissionthatwillbeaduplicateofitsimmediatepredecessor:

[app/search.component.ts]

import{Component}from'@angular/core';

import{APIService}from'./api.service';

import{FormControl}from'@angular/forms';

@Component({

selector:'search',

template:`

<input[formControl]="queryField">

<p*ngFor="letresultofresults">{{result}}</p>

`

})

exportclassSearchComponent{

results:Array<string>=[];

queryField:FormControl=newFormControl();

constructor(privateapiService_:APIService){

this.queryField.valueChanges

.debounceTime(200)

.distinctUntilChanged()

.subscribe(query=>this.apiService_

.search(query)

.subscribe(result=>this.results.push(result)));

}

}

FlatteningObservables

YouhavechainedquiteafewRxJSmethodsuptothispoint,andseeingnestedsubscribe()

Page 364: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

invocationsmightfeelabitfunnytoyou.ItshouldmakesensesincethevalueChangesObservablehandlerisinvokingaservicemethod,whichreturnsaseparateObservable.InTypeScript,thisiseffectivelyrepresentedasObservable<Observable<string>>.Gross!

Sinceyouonlyreallycareabouttheemittedstringscomingfromtheservicemethod,itwouldbemucheasiertojustcombinealltheemittedstringscomingoutofeachreturnedObservableintoasingleObservable.Fortunately,RxJSmakesthiseasywithflatMap,whichflattensalltheemissionsfromtheinnerObservablesintoasingleouterObservable.InTypeScript,usingflatMapwouldconvertthisintoObservable<string>,whichisexactlywhatyouneed:

[app/search.component.ts]

import{Component}from'@angular/core';

import{APIService}from'./api.service';

import{FormControl}from'@angular/forms';

@Component({

selector:'search',

template:`

<input[formControl]="queryField">

<p*ngFor="letresultofresults">{{result}}</p>

`

})

exportclassSearchComponent{

results:Array<string>=[];

queryField:FormControl=newFormControl();

constructor(privateapiService_:APIService){

this.queryField.valueChanges

.debounceTime(200)

.distinctUntilChanged()

.flatMap(query=>this.apiService_.search(query))

.subscribe(result=>this.results.push(result));

}

}

Handlingunorderedresponses

Whentestinginputnow,youwillsurelynoticethatthedelayintentionallyintroducedinsidetheAPIservicewillcausetheresponsestobereturnedoutoforder.Thisisaprettyeffectivesimulationofnetworklatency,soyou'llneedagoodwayofhandlingthis.

Ideally,youwouldliketobeabletothrowoutObservablesthatareinflightonceyouhaveamorerecentquerytoexecute.Forexample,considerthatyou'vetypedgandtheno.Nowoncethesecondqueryforgoisreturnedandifthefirstqueryforghasn'treturnedyet,you'dliketojustthrowitoutandforgetaboutitsincetheresponseisnowirrelevant.

RxJSalsomakesthisveryeasywithswitchMap.ThisdoesthesamethingsasflatMap,butit

Page 365: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

willunsubscribefromanyin-flightObservablesthathavenotemittedanyvaluesyet:

[app/search.component.ts]

import{Component}from'@angular/core';

import{APIService}from'./api.service';

import{FormControl}from'@angular/forms';

@Component({

selector:'search',

template:`

<input[formControl]="queryField">

<p*ngFor="letresultofresults">{{result}}</p>

`

})

exportclassSearchComponent{

results:Array<string>=[];

queryField:FormControl=newFormControl();

constructor(privateapiService_:APIService){

this.queryField.valueChanges

.debounceTime(200)

.distinctUntilChanged()

.switchMap(query=>this.apiService_.search(query))

.subscribe(result=>this.results.push(result));

}

}

YourAutoCompleteinputshouldnowbedebouncedanditshouldignoreredundantrequestsandreturnin-orderresults.

Page 366: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Therearealotofmovingpiecesgoingoninthisrecipe,butthecorethemeremainsthesame:RxJSObservablesexposemanymethodsthatcanpipetheoutputfromoneobservableintoanentirelydifferentobservable.Itcanalsocombinemultipleobservablesintoasingleobservable,aswellasintroducestate-dependentoperationsintoastreamoftheinput.Attheendofthisrecipe,thepowerofreactiveprogrammingshouldbeobvious.

Page 367: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoBasicUtilizationofObservableswithHTTPdemonstratesthebasicsofhowtouseanobservableinterfaceImplementingaPublish-SubscribemodelusingSubjectsshowsyouhowtoconfigureinputandoutputforRxJSObservablesCreatinganObservableauthenticationserviceusingBehaviorSubjectsinstructsyouonhowtoreactivelymanagethestateinyourapplicationBuildingageneralizedPublish-Subscribeservicetoreplace$broadcast,$emit,and$onassemblesarobustPubSubmodelforconnectingapplicationcomponentswithchannels

Page 368: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter6.TheComponentRouterThischapterwillcoverthefollowingrecipes:

SettingupanapplicationtosupportsimpleroutesNavigatingwithrouterLinksNavigatingwiththeRouterserviceSelectingLocationStrategyforPathConstructionBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveImplementingnestedviewswithrouteparametersandchildroutesWorkingwithMatrixURLparametersandroutingarraysAddingrouteauthenticationcontrolswithrouteguards

Page 369: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionFewfeaturesofAngular2shouldbeanticipatedmorethantheComponentRouter.ThisnewroutingimplementationaffordsyouadazzlingarrayoffeaturesthatweremissingorseverelylackinginAngular1.

Angular2implementsmatrixparameters;thisisanentirelynewsyntaxforURLstructures.OriginallyproposedbyTimBerners-Leein1996,thissemicolon-basedsyntaxgivesyoutheabilitytorobustlyassociateparametersnotjustwithasingleURL,butwithdifferentlevelsinthatURL.YourapplicationcannowintroduceanadditionaldimensionofapplicationstateintheURLs.

Additionally,ComponentRoutergivesyouamethodofelegantlynestingviewswithineachotheraswellasasimplewayofdefiningroutesandlinkstothesecomponenthierarchies.Foryou,thismeansyourapplicationscantrulytakemaximaladvantageofdefininganapplicationasanindependentmodule.

Finally,ComponentRouterfullyembracesintegrationwithObservablestructuresandprovidesyouwithsomebeautifulwaysofnavigatingandcontrollingnavigationwithinyourapplication.

Page 370: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SettingupanapplicationtosupportsimpleroutesCentraltothebehaviorofsingle-pageapplicationsistheabilitytoperformnavigationwithoutaformalbrowserpagereload.Angular2iswell-equippedtoworkaroundthedefaultbrowserpagereloadbehaviorandallowyoutodefinearoutingstructurewithinit,whichwillmakeitlookandfeellikeactualpagenavigation.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6214.

Page 371: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhavethefollowingfunctiondefinedglobally:

functionvisit(uri){

//Forthisrecipe,youdon'tcareaboutthestateortitle

window.history.pushState(null,null,uri);

}

ThepurposeofthisistomerelyallowyoutonavigateinsidethebrowserfromJavaScriptusingtheHTML5HistoryAPI.

Page 372: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...InorderforAngulartosimulatepagenavigationinsidethebrowser,thereareseveralstepsyoumusttaketocreateanavigablesingle-pageapplication.

SettingthebaseURL

ThefirststepinconfiguringyourapplicationistospecifythebaseURL.ThisinstructsthebrowserwhatnetworkrequestsperformedwitharelativeURLshouldbeginwith.

AnytimethepagemakesarequestusingarelativeURL,whengeneratingthenetworkrequest,itwillusethecurrentdomainandthenappendtherelativeURL.ForrelativeURLs,aprepended"/"meansitwillalwaysusetherootdirectory.Iftheforwardslashisunavailable,therelativepathwillprependwhateverisspecifiedasthebasehref.AnyURLbehavior,includingrequestingstaticresources,anchorlinks,andthehistoryAPI,willexhibitthisbehavior.

Note

<base>isnotapartofAngularbutratheradefaultHTML5element.

Herearesomeexamplesofthis:

[Example1]

//<basehref="/">

//initialpagelocation:foo.com

visit('bar');

//newpagelocation:foo.com/bar

visit('bar');

//newpagelocation:foo.com/bar

//Thebrowserrecognizesthatthisisarelativepath

//withnoprepended/andsoitwillvisitthepageatthe

//same"depth"asbefore.

visit('bar/');

//newpagelocation:foo.com/bar/

//Sameasbefore,butthetrailingslashwillbeimportantonce

//youinvokethisagain.

visit('bar/');

//newpagelocation:foo.com/bar/bar/

//ThebrowserrecognizesthattheURLendswitha/,andso

//visitingarelativepathistreatedasanavigationintoa

//subpath

visit('/qux');

//newpagelocation:foo.com/qux

//Witha/prependedtotheURL,thebrowserrecognizesthatit

Page 373: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

//shouldnavigatefromtherootdomain

[Example2]

//<basehref="xyz/">

//initialpagelocation:foo.com

visit('bar');

//newpagelocation:foo.com/xyz/bar

//BaseURLisprependedtotherelativeURL

visit('bar');

//newpagelocation:foo.com/xyz/bar

//Aswasthecasebefore,thelocalpathistreatedthesame

//bythebrowser

visit('/qux');

//newpagelocation:foo.com/qux

//Notethatinthiscase,youspecifiedarelativepath

//originatingfromtherootdomain,sothebasehrefisignored

Definingroutes

Next,youneedtodefinewhatyourapplication'sroutesare.Forthepurposeofthisrecipe,itismoreimportanttounderstandthesetupofroutingthanhowtodefineandnavigatebetweenroutes.So,fornow,youwilljustdefineasinglecatchallroute.

Asyoumightsuspect,routeviewsinAngular2aredefinedascomponents.Eachroutepathisrepresentedattheveryleastbythestringthatthebrowser'slocationwillmatchagainstandthecomponentthatitwillmapto.ThiscanbedonewithanobjectimplementingtheRoutesinterface,whichisanarrayofroutedefinitions.

Itmakessensethattheroutedefinitionsshouldhappenveryearlyintheapplicationinitialization,soyou'lldoitinsidethetop-levelmoduledefinition.

First,createyourviewcomponentthatthisroutewillmapto:

[app/default.component.ts]

import{Component}from'@angular/core';

@Component({

template:'Defaultcomponent!'

})

exportclassDefaultComponent{}

Next,whereveryourapplicationmoduleisdefined,importRouterModuleandtheRoutesinterface,namelyDefaultComponent,anddefineacatchallrouteinsidetheRoutesarray:

[app/app.module.ts]

Page 374: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

constappRoutes:Routes=[

{path:'**',component:DefaultComponent}

];

@NgModule({

imports:[

BrowserModule

],

declarations:[

DefaultComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Providingroutestotheapplication

You'vedefinedtheroutesinanobject,butyourapplicationstillisnotawarethattheyexist.YoucandothiswiththeforRootmethoddefinedinRouterModule.Thisfunctiondoesallthedirtyworkofinstallingyourroutesintheapplicationaswellaspassingalonganumberofroutingprovidersforuseelsewhereintheapplication:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

constappRoutes:Routes=[

{path:'**',component:DefaultComponent}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

RootComponent

],

Page 375: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Withthis,yourapplicationisfullyconfiguredtounderstandtherouteyouhavedefined.

RenderingroutecomponentswithRouterOutlet

Thecomponentneedsaplacetoberendered,andinAngular2,thistakestheformofaRouterOutlettag.Thisdirectivewillbetargetedbythecomponentattachedtotheactiveroute,andthecomponentwillberenderedinsideit.Tokeepthingssimple,inthisrecipe,youcanusethedirectiveinsidetherootapplicationcomponent:

[app/app.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{}

That'sall!YourapplicationnowhasasingleroutedefinedthatwillrenderDefaultComponentinsideRootComponent.

Page 376: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Thisrecipedoesn'tshowaverycomplicatedexampleofrouting,sinceeverypossibleroutethatyoucanvisitwillleadyoutothesamecomponent.

Nonetheless,itdemonstratesseveralfundamentalprinciplesofAngularrouting:

Initsmostbasicform,arouteiscomprisedofastringpath(matchedagainstthebrowserpath)andthecomponentthatshouldberenderedwhenthisrouteisactive.RoutesareinstalledviaRouterModule.Inthisexample,sincethereisonlyonemodule,youcandothisonceusingforRoot().However,keepinmindthatyoucanbreakyourroutingstructureintopiecesandbetweendifferentNgModules.Navigatingtoaroutewillcauseacomponenttoberenderedinsideadifferentcomponent-morespecifically,whereverthe<router-outlet>tagexists.Therearemanywaysinwhichthiscanbeconfiguredandmademorecomplex,butforthepurposeofthissimplemodule,youdon'tneedtoworryaboutthesedifferentways.

Page 377: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Angular2applicationswillnotraiseissueswhenoperatingwithnoformofrouting.IfyourapplicationdoesnotneedtounderstandandmanagethepageURL,thenfeelfreetototallydiscardtheroutingfilesandmodulesfromyourapplication.

Initialpageload

Theflowyouarehopingyouruserswouldgothroughisasfollows:

1. Theuservisitshttp://www.foo.com/.2. Theservermatchestheemptyroutetoindex.html.3. Thepageloads,requestingstaticfiles.4. TheAngularstaticfilesareloadedandapplicationisbootstrapped.5. Theuserclicksonthelinksandnavigatesaroundthesite.6. SinceAngulariswhollymanagingthenavigationandrouting,everythingworksasexpected.

Thisistheidealcase.Consideradifferentcase:

1. Theuserhasalreadyvisitedhttp://www.foo.com/beforeandbookmarkedit.2. Theuserentersfoo.com/barintheirURLbarandnavigatestoitfromtheredirectly.3. Theserverseestherequestpathas/barandtriestohandletherequest.

Dependingonhowyourserverisconfigured,thismightcauseproblemsforyou.Thisisbecausethelasttimetheuservisitedfoo.com/bar,norequestforthatresourcereachedtheserverbecauseAngularwasonlyemulatingarealnavigationevent.

Thisscenarioisdiscussedelsewhereinthischapter,butkeepinmindthatwithoutacorrectlyconfiguredserver,theuserinthesecondcasemightseea404pageerrorinsteadofyourapplication.

Page 378: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoNavigatingwithrouterLinksdemonstrateshowtonavigatearoundAngularapplicationsNavigatingwiththeRouterserviceusesanAngularservicetonavigatearoundanapplicationBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstate

Page 379: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NavigatingwithrouterLinksNavigatingaroundasinglepageapplicationisafundamentaltask,andAngularoffersyouabuilt-indirective,routerLink,toaccomplishthis.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/9983/.

Page 380: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththeapplicationsetupassembledintheSettingupanapplicationtosupportsimpleroutesrecipe.

Yourgoalistoaddanadditionalroutetothisapplicationaccompaniedbyacomponent;also,youwanttobeabletonavigatebetweenthemusinglinks.

Page 381: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Tobegin,createanothercomponent,ArticleComponent,andanassociatedroute:

[app/article/article.component.ts]

import{Component}from'@angular/core';

@Component({

template:'Articlecomponent!'

})

exportclassArticleComponent{}

Next,installanarticlerouteaccompaniedbythisnewcomponent:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ArticleComponent}from'./article.component';

constappRoutes:Routes=[

{path:'article',component:ArticleComponent},

{path:'**',component:DefaultComponent}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ArticleComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Withtheroutesdefined,youcannowbuildarudimentarynavbarcomprisedofrouterLinks.Themarkupsurroundingthe<router-outlet>tagwillremainirrespectiveoftheroute,sotherootappcomponentseemslikeasuitableplaceforthenavlinks.

TherouterLinkdirectiveisavailableaspartofRouterModule,soyoucangostraighttoadding

Page 382: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

someanchortags:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="''">Default</a>

<a[routerLink]="'article'">Article</a>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{}

Inthiscase,sincetheroutesaresimpleandstatic,bindingrouterLinktoastringisallowed.routerLinkalsoacceptsthearraynotation:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="['']">Default</a>

<a[routerLink]="['article']">Article</a>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{}

Tip

Forthepurposeofthisrecipe,thearraynotationdoesn'taddanything.However,whendevelopingmorecomplicatedURLstructures,thearraynotationbecomesuseful,asitallowsyoutogeneratelinksinapiecewisefashion.

Page 383: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Atahighlevel,thisisnodifferentthanthebehaviorofavanillahrefattribute.Afterall,theroutesbehaveinthesamewayandarestructuredsimilarly.TheimportantdifferencehereisthatusingarouterLinkdirectiveinsteadofhrefallowsyoutomovearoundyourapplicationtheAngularway,withouteverhavingtheanchortagclickinterpretedbythebrowserasanon-Angularnavigationevent.

Page 384: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Ofcourse,therouterLinkdirectiveisalsosuperiorasitismoreextensibleasatoolfornavigating.SinceitisanHTMLattributeafterall,there'snoreasonrouterLinkcan'tbeattachedto,forexample,abuttoninstead:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<button[routerLink]="['']">Default</button>

<button[routerLink]="['article']">Article</button>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{}

What'smore,you'llalsonotethatthearraynotationallowsthedynamicgenerationoflinksviaallofthetremendousdatabindingthatAngularaffordsyou:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="[defaultPath]">Default</a>

<a[routerLink]="[articlePath]">Article</a>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{

defaultPath:string='';

articlePath:string='article';

}

AstheURLstructuregetsevermoreadvanced,itwillbeeasytoseehowacleverapplicationofdatabindingcouldmakeforsomeveryelegantdynamiclinkgeneration.

Routeorderconsiderations

TheorderingofroutesinsidetheRoutesdefinitionspecifiesthedescendingpriorityofeachofthem.Inthisrecipe'sexample,supposeyouweretoreversetheorderoftheroutes:

Page 385: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ArticleComponent}from'./article.component';

constappRoutes:Routes=[

{path:'**',component:DefaultComponent},

{path:'article',component:ArticleComponent}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ArticleComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Whenexperimenting,youwillfindthatthebrowser'sURLchangescorrectlywiththevariousrouterLinkinteractions,butbothrouteswilluseDefaultComponentastherenderedview.Thisissimplybecausealltheroutesmatchthe**catchall,andAngulardoesn'tbothertotraversetheroutesanyfurtheronceithasamatchingroute.Keepthisinmindwhenauthoringlargeroutetables.

Page 386: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoSettingupanapplicationtosupportsimpleroutesshowsyouthebasicsofAngularroutingNavigatingwiththeRouterserviceusesanAngularservicetonavigatearoundanapplicationBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstateImplementingnestedviewswithrouteparametersandchildroutesgivesanexampleofhowtoconfigureAngularURLstosupportnestinganddatapassingWorkingwithmatrixURLparametersandroutingarraysdemonstratesAngular'sbuilt-inmatrixURLsupport

Page 387: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NavigatingwiththeRouterserviceThecompaniontousingrouterLinkinsidethetemplatetonavigateisdoingitfrominsideJavaScript.Angularexposesthenavigate()methodfrominsideaservice,whichallowsyoutoaccomplishexactlythis.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/8004/.

Page 388: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththeapplicationthatexistsattheendoftheHowtodoit...sectionoftheNavigatingwithrouterLinksrecipe.

Yourgoalistoaddanadditionalrouteaccompaniedbyacomponenttothisapplication;also,youwishtobeabletonavigatebetweenthemusinglinks.

Page 389: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...InsteadofusingrouterLink,whichisthemostsensiblechoiceinthissituation,youcanalsotriggeranavigationusingtheRouterservice.First,addnavbuttonsandattachsomeemptyclickhandlerstothem:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<button(click)="visitDefault()">Default</button>

<button(click)="visitArticle()">Article</button>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{

visitDefault():void{}

visitArticle():void{}

}

Next,importtheRouterserviceanduseitsnavigate()methodtochangethepagelocation:

[app/root.component.ts]

import{Component}from'@angular/core';

import{Router}from'@angular/router';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<button(click)="visitDefault()">Default</button>

<button(click)="visitArticle()">Article</button>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{

constructor(privaterouter:Router){}

visitDefault():void{

this.router.navigate(['']);

}

visitArticle():void{

this.router.navigate(['article']);

}

}

Withthisaddition,youshouldbeabletonavigatearoundyourapplicationinthesamewayyou

Page 390: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

didbefore.

Page 391: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...TheRouterserviceexposesanAPIwithwhichyoucancontrolyourapplication'snavigationbehavior,amongmanyotherthings.Itsnavigate()methodacceptsanarray-structuredroute,whichoperatesidenticallytotheArraysboundtorouterLink.

Page 392: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Obviously,thisisanutterantipatternforbuildingapplicationsthataredesignedtoscale.Inthisscenario,routerLinkisamuchmoresuccinctandeffectivechoiceforbuildingasimplenavbar.Nevertheless,theRouterserviceisanequallyeffectivetoolfortraversinganAngularapplication'sroutestructure.

Page 393: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoNavigatingwithrouterLinksdemonstrateshowtonavigatearoundAngularapplicationsBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstateWorkingwithmatrixURLparametersandroutingarraysdemonstratesAngular'sbuilt-inmatrixURLsupportAddingrouteauthenticationcontrolswithrouteguardsdetailstheentireprocessofconfiguringprotectedroutesinyourapplication

Page 394: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SelectingaLocationStrategyforpathconstructionAsimplebutimportantchoiceforyourapplicationiswhichtypeofLocationStrategyyouwanttomakeuseof.ThefollowingtwoURLsareequivalentwhentheirrespectiveLocationStrategyisselected:

PathLocationStrategy:foo.com/barHashLocationStrategy:foo.com/#/bar

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/1355/.

Page 395: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Angular2willdefaulttoPathLocationStrategy.ShouldyouwanttoselectHashLocationStrategy,itcanbeimportedfromthe@angular/commonmodule.Onceimported,itcanbelistedasaproviderinsideanobjectliteral:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ArticleComponent}from'./article.component';

import{LocationStrategy,HashLocationStrategy}

from'@angular/common';

constappRoutes:Routes=[

{path:'article',component:ArticleComponent},

{path:'**',component:DefaultComponent},

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ArticleComponent,

RootComponent

],

providers:[

{provide:LocationStrategy,useClass:HashLocationStrategy}

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Withthisaddition,yourapplicationwilltransitiontoprefix#/toallapplication-definedURLs.Thiswilloccurintransparencewiththerestofyourapplication,whichcanuseitsroutingdefinitionsnormallywithouthavingtoworryaboutprefixing#/.

Page 396: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Therearetradeoffsforeachofthesestrategies.AstheAngulardocumentationnotes,onceyouchooseone,itisinadvisabletoswitchtotheothersincebookmarks,SEO,anduserhistorywillallbecoupledtotheURLstrategyutilizedduringthatvisit.

PathLocationStrategy:

Here,theURLsappearnormaltotheenduserTheservermustbeconfiguredtohandlepageloadsfromanyapplicationpathThisallowsthehybridserver-siderenderingofroutesforimprovedperformance

HashLocationStrategy:

Here,theURLsmaylookfunnytotheenduser.Noserverconfigurationisrequirediftherootdomainservesindex.html.Thisisagoodoptionifyouonlywanttoservestaticfiles(forexample,anAmazonAWS-basedsite).Itcannotbeeasilyintermixedwithhybridserver-siderendering.

ConfiguringyourapplicationserverforPathLocationStrategy

Angularissmartenoughtorecognizethebrowserstateandmanageitaccordinglyoncebootstrappingoccurs.However,bootstrappingrequiresaninitialloadofthestaticcompiledJSassets,whichwillbootstrapAngularoncethebrowserloadsthem.Whentheuserinitiallyvisitsarootdomain,suchasfoo.com,theserverisnormallyconfiguredtorespondwithindex.html,whichwillinturnrequestthestaticassetsatrendertime.So,Angularwillwork!

However,incaseswheretheuserinitiallyvisitsanon-rootpath,suchasfoo.com/bar,thebrowserwillsendarequesttotheserveratfoo.com/bar.Ifyouaren'tcarefulwhensettingupyourserver,acommonmistakeyoumaycommitishavingonlytherootfoo.compathreturnindex.html.

InorderforPathLocationStrategytoworkcorrectlyinallcases,youmustconfigureyourwebservertosetupacatchallrouteforalltherequeststhathavepathsintendedforthesingle-pageapplication'srouteintheclient,andtoinvariablyreturnindex.html.Inotherwords,visitingfoo.com,foo.com/bar,orfoo.com/bar/bazasthefirstpageinthebrowserwillallreturnthesamething:index.html.Onceyoudothis,postbootstrapAngularwillexaminethecurrentbrowserpathandrecognizewhichpathitisonandwhatviewneedstobedisplayed.

Page 397: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

BuildingstatefulroutebehaviorwithRouterLinkActiveItisoftenthecasewhenbuildingapplicationsthatyouwillwanttobuildfeaturesthatwouldinvolvewhichpagetheapplicationiscurrentlyon.Whenthisisaone-timeinspection,itisn'taproblem,asbothAngularanddefaultbrowserAPIsallowyoutoeasilyinspectthecurrentpage.

ThingsgetabitstickierwhenyouwantthestateofthepagetoreflectthestateoftheURL,forexample,ifyouwanttovisuallyindicatewhichlinkcorrespondstothecurrentpage.Afrom-scratchimplementationofthiswouldrequiresomesortofstatemachinethatwouldknowwhennavigationeventsoccurandwhatandhowtomodifyateachgivenroute.

Fortunately,Angular2givesyousomeexcellenttoolstodothisrightoutofthebox.

Note

Thecode,links,andaliveexampleofthisareallavailableathttp://ngcookbook.herokuapp.com/3308/.

Page 398: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththeArrayandanchor-tag-basedimplementationshownintheNavigatingwithrouterLinksrecipe.

YourgoalistouseRouterLinkActivetointroducesomesimplestatefulroutebehavior.

Page 399: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...RouterLinkActiveallowsyoutoconditionallyapplyclasseswhenthecurrentroutematchesthecorrespondingrouterLinkonthesameelement.ProceeddirectlytoaddingitasanattributedirectivetoeachlinkaswellasamatchingCSSclass:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="['']"

[routerLinkActive]="'active-navlink'">Default</a>

<a[routerLink]="['article']"

[routerLinkActive]="'active-navlink'">Article</a>

<router-outlet></router-outlet>

`,

styles:[`

.active-navlink{

color:red;

text-transform:uppercase;

}

`]

})

exportclassRootComponent{}

Thisisallyouneedforlinkstobecomeactive!YouwillnoticethatAngularwillconditionallyapplytheactive-navlinkclassbasedonthecurrentroute.

However,whentestingthis,youwillnoticethatthe/articleroutemakesboththelinksappearactive.Thisisduetothefactthatbydefault,AngularmarksallrouterLinksthatmatchthecurrentrouteasactive.

Note

Thisbehaviorisusefulincaseswhereyoumaywanttoshowahierarchyoflinksasactiveforexample,atroute/user/123/detail,itcouldmakesensethattheseparatelinks/user,/user/123,and/user/123/detailareallshownasactive.

However,inthecaseofthisrecipe,thisbehaviorisnotusefultoyou,andAngularhasanotherrouterdirective,routerLinkActiveOptions,whichbindstoanoptionsobject.Theexactpropertyinsidetheoptionsobjectisusefulinthiscase;itcontrolswhethertheactivestateshouldonlybeappliedincasesofanexactmatch:

[app/root.component.ts]

Page 400: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Component}from'@angular/core';

import{Router}from'@angular/router';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="['']"

[routerLinkActive]="'active-navlink'"

[routerLinkActiveOptions]="{exact:true}">Default</a>

<a[routerLink]="['article']"

[routerLinkActive]="'active-navlink'"

[routerLinkActiveOptions]="{exact:true}">Article</a>

<router-outlet></router-outlet>

`,

styles:[`

.active-navlink{

color:red;

text-transform:uppercase;

}

`]

})

exportclassRootComponent{}

Nowyouwillfindthateachlinkwillonlybeactiveatitsrespectiveroute.

Page 401: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...TherouterLinkActiveimplementationsubscribestonavigationchangeeventsthatAngularemitsfromtheRouterservice.WhenitseesaNavigationEndevent,itperformsanupdateofalltheattachedHTMLtags,whichincludesaddingandstrippingapplicable"active"CSSclassesthattheelementisboundtoviathedirective.

Page 402: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...IfyouneedtobindrouterLinkActivetoadynamicvalue,theprecedingsyntaxwillallowyoutodoexactlythat.Forexample,youcanbindtoa

component

memberand

modify

itelsewhere,andAngularwillhandleeverythingforyou.However,ifthisisnotrequired,AngularwillhandlerouterLinkActivewithoutthedatabindingbrackets.Inthiscase,thevalueofthedirectivenolongerneedstobeanAngularexpression,soyoucanremovethenestedquotes.

Thefollowingisbehaviorallyidentical:

[app/root.component.ts]

import{Component}from'@angular/core';

import{Router}from'@angular/router';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="['']"

routerLinkActive="active-navlink"

[routerLinkActiveOptions]="{exact:true}">

Default</a>

<a[routerLink]="['article']"

routerLinkActive="active-navlink"

[routerLinkActiveOptions]="{exact:true}">

Article</a>

<router-outlet></router-outlet>

`,

styles:[`

.active-navlink{

color:red;

text-transform:uppercase;

}

`]

})

exportclassRootComponent{}

Page 403: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoSettingupanapplicationtosupportsimpleroutesshowsyouthebasicsofAngularroutingNavigatingwithrouterLinksdemonstrateshowtonavigatearoundAngularapplicationsBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstateImplementingnestedviewswithrouteparametersandchildroutesgivesanexampleofhowtoconfigureAngularURLstosupportnestinganddatapassingAddingrouteauthenticationcontrolswithrouteguardsdetailstheentireprocessofconfiguringprotectedroutesinyourapplication

Page 404: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ImplementingnestedviewswithrouteparametersandchildroutesAngular2'scomponentrouteroffersyouthenecessaryconceptofchildroutes.Asyoumightexpect,thisbringstheconceptofrecursivelydefinedviewstothetable,whichaffordsyouanincrediblyusefulandelegantwayofbuildingyourapplication.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/7892/.

Page 405: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththeArrayandanchor-tag-basedimplementationshowninNavigatingwithrouterLinksrecipe.

Yourgoalistoextendthissimpleapplicationtoinclude/article,whichwillbethelistview,and/article/:id,whichwillbethedetailview.

Page 406: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...First,modifytheroutestructureforthissimpleapplicationbyextendingthe/articlepathtoincludeitssubpaths:/and/:id.Routesaredefinedhierarchically,andeachroutecanhavechildroutesusingthechildrenproperty.

Addingaroutingtargettotheparentcomponent

First,youmustmodifytheexistingArticleComponentsothatitcancontainchildviews.Asyoumightexpect,thechildviewisrenderedinexactlythesamewayasitisdonefromtherootcomponent,usingRouterOutlet:

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

template:`

<h2>Article</h2>

<router-outlet></router-outlet>

`

})

exportclassArticleComponent{}

Thiswon'tdoanythingyet,butaddingRouterOutletdescribestoAngularhowroutecomponenthierarchiesshouldberendered.

Definingnestedchildviews

Inthisrecipe,youwouldliketohavetheparentArticleComponentcontainachildview,eitherArticleListComponentorArticleDetailComponent.Forthesimplicityofthisrecipe,youcanjustdefineyourlistofarticlesasanarrayofintegers.

Definetheskeletonofthesetwocomponentsasfollows:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

template:`

<h3>ArticleList</h3>

`

})

exportclassArticleListComponent{

articleIds:Array<number>=[1,2,3,4,5];

}

[app/article-detail.component.ts]

Page 407: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Component}from'@angular/core';

@Component({

template:`

<h3>ArticleDetail</h3>

<p>Showingarticle{{articleId}}</p>

`

})

exportclassArticleDetailComponent{

articleId:number;

}

Definingthechildroutes

Atthispoint,nothingintheapplicationyetpointstoeitherofthesechildroutes,soyou'llneedtodefinethemnow.

ThechildrenpropertyofarouteshouldjustbeanotherRoute,whichshouldrepresentthenestedroutesthatareappendedtotheparentroute.

Note

Inthisway,youaredefiningasortofrouting"tree,"whereeachrouteentrycanhavemanychildroutesdefinedrecursively.Thiswillbediscussedingreaterdetaillaterinthischapter.

Furthermore,youshouldalsousetheURLparameternotationtodeclare:articleIdasavariableintheroute.Thisallowsyoutopassvaluesinsidetherouteandthenretrievethesevaluesinsidethecomponentthatisrendered.

Addtheseroutedefinitionsnow:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ArticleComponent}from'./article.component';

import{ArticleListComponent}from'./article-list.component';

import{ArticleDetailComponent}from'./article-detail.component';

constappRoutes:Routes=[

{path:'article',component:ArticleComponent,

children:[

{path:'',component:ArticleListComponent},

{path:':articleId',component:ArticleDetailComponent}

]

},

{path:'**',component:DefaultComponent},

];

Page 408: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ArticleComponent,

ArticleListComponent,

ArticleDetailComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

You'llnotethatArticleListComponentiskeyedbyanemptystring.Thisshouldmakesense,aseachoftheseroutesarejoinedtotheirparentroutestocreatethefullroute.Ifyouweretojoineachrouteinthistreewithitsancestralpathtogetthefullroute,theroutedefinitionyou'vejustcreatedwouldhavethefollowingthreeentries:

/article=>ArticleComponent

ArticleListComponent

/article/4=>ArticleComponent

ArticleDetailComponent<articleId=4>

/**=>DefaultComponent

Note

Notethatinthiscase,thenumberofactualroutescorrespondstothenumberofleavesoftheURLtreesincethearticleparentroutewillalsomaptothechildarticle's+''route.Dependingonhowyouconfigureyourroutestructure,theleaf/routeparitywillnotalwaysbethecase.

Definingchildviewlinks

Withtheroutesbeingmappedtothechildcomponents,youcanfleshoutthechildviews.StartingwithArticleList,createarepeatertogeneratethelinkstoeachofthechildviews:

[app/article-list.component.ts]

import{Component}from'@angular/core';

@Component({

template:`

<h3>ArticleList</h3>

<p*ngFor="letarticleIdofarticleIds">

<a[routerLink]="articleId">

Article{{articleId}}

Page 409: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

</a>

</p>

`

})

exportclassArticleListComponent{

articleIds:Array<number>=[1,2,3,4,5];

}

Note

NotethatrouterLinkislinkingtotherelativepathofthedetailview.Sincethecurrentpathforthisviewis/article,arelativerouterLinkof4willnavigatetheapplicationto/article/4uponaclick.

Theselinksshouldwork,butwhenyouclickonthem,theywilltakeyoutothedetailviewthatcannotdisplayarticleIdfromtheroutesinceyouhavenotextractedityet.

InsideArticleDetailComponent,createalinkthatwilltaketheuserbacktothearticle/route.Sinceroutesbehavelikedirectories,youcanjustusearelativepaththatwilltaketheuseruponelevel:

[app/article-detail.component.ts]

import{Component}from'@angular/core';

@Component({

template:`

<h3>ArticleDetail</h3>

<p>Showingarticle{{articleId}}</p>

<a[routerLink]="'../'">Backup</a>

`

})

exportclassArticleDetailComponent{

articleId:number;

}

Extractingrouteparameters

AcrucialdifferencebetweenAngular1and2istherelianceonObservableconstructs.Inthecontextofrouting,Angular2wieldsObservablestoencapsulatethatroutingoccursasasequenceofeventsandthatvaluesareproducedatdifferentstatesintheseeventsandwillbereadyeventually.

Moreconcretely,routeparamsinAngular2arenotexposeddirectly,butratherthroughanObservableinsideActivatedRoute.YoucansetObserveronitsparamsObservabletoextracttherouteparamsoncetheyareavailable.

InjecttheActivatedRouteinterfaceandusetheparamsObservabletoextractarticleIdand

Page 410: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

assignittotheArticleDetailComponentinstancemember:

[app/article-detail/article-detail.component.ts]

import{Component}from'@angular/core';

import{ActivatedRoute}from'@angular/router';

@Component({

template:`

<h3>ArticleDetail</h3>

<p>Showingarticle{{articleId}}</p>

<a[routerLink]="'../'">Backup</a>

`

})

exportclassArticleDetailComponent{

articleId:number;

constructor(privateactivatedRoute_:ActivatedRoute){

activatedRoute_.params

.subscribe(params=>this.articleId=params['articleId']);

}

}

Withthis,youshouldbeabletoseethearticleIdparameterinterpolatedintoArticleDetailComponent.

Page 411: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Inthisapplication,youhavenestedcomponents,AppComponentandArticleComponent,bothofwhichcontainRouterOutlet.Angularisabletotaketheroutinghierarchyyoudefinedandapplyittothecomponenthierarchythatitmapsto.Morespecifically,foreveryRouteyoudefineinyourroutinghierarchy,thereshouldbeanequalnumberofRouterOutletsinwhichtheycanrender.

Page 412: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Tosome,itwillfeelstrangetoneedtoextracttherouteparamsfromanObservableinterface.Ifthissolutionfeelsabitclunkytoyou,therearewaysoftidyingitup.

Refactoringwithasyncpipes

RecallthatAngularhastheabilitytointerpolateObservabledatadirectlyintothetemplateasitbecomesready.EspeciallysinceyoushouldonlyeverexpecttheparamObservabletoemitonce,youcanuseittoinsertarticleIdintothetemplatewithoutexplicitlysettinganObserver:

[app/article-detail.component.ts]

import{Component}from'@angular/core';

import{ActivatedRoute}from'@angular/router';

@Component({

template:`

<h3>ArticleDetail</h3>

<p>Showingarticle

{{(activatedRoute.params|async).articleId}}</p>

<a[routerLink]="'../'">Backup</a>

`

})

exportclassArticleDetailComponent{

constructor(activatedRoute:ActivatedRoute){}

}

Eventhoughthisworksperfectlywell,usingaprivatereferencetoaninjectedservicedirectlyintothetemplatemayfeelabitfunnytoyou.AsuperiorstrategyistograbareferencetothepublicObservableinterfaceyouneedandinterpolatethatinstead:

[app/article-detail.component.ts]

import{Component}from'@angular/core';

import{Observable}from'rxjs/Observable';

import{ActivatedRoute,Params}from'@angular/router';

@Component({

template:`

<h3>ArticleDetail</h3>

<p>Showingarticle{{(params|async).articleId}}</p>

<a[routerLink]="'../'">Backup</a>

`

})

exportclassArticleDetailComponent{

params:Observable<Params>;

constructor(privateactivatedRoute_:ActivatedRoute){

this.params=activatedRoute_.params;

}

Page 413: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

Page 414: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoNavigatingwithrouterLinksdemonstrateshowtonavigatearoundAngularapplicationsNavigatingwiththeRouterserviceusesanAngularservicetonavigatearoundanapplicationBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstateWorkingwithmatrixURLparametersandroutingarraysdemonstratesAngular'sbuilt-inmatrixURLsupportAddingrouteauthenticationcontrolswithrouteguardsdetailstheentireprocessofconfiguringprotectedroutesinyourapplication

Page 415: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WorkingwithmatrixURLparametersandroutingarraysAngular2introducesnativesupportforanawesomefeaturethatseemstobefrequentlyoverlooked:matrixURLparameters.Essentially,theseallowyoutoattachanarbitraryamountofdatainsideaURLtoanyroutinglevelinAngular,andgivingyoutheabilitytoreadthatdataoutasaregularURLparameter.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4553/.

Page 416: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththecodecreatedattheendoftheHowtodoit...sectioninImplementingnestedviewswithrouteparametersandchildroutes.

YourgoalistopassarbitrarydatatoboththeArticleListandArticleDetaillevelsofthisapplicationviaonlytheURL.

Page 417: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...routerLinkarraysareprocessedserially,soanystringthatwillbecomepartoftheURLthatisfollowedbyanobjectwillhavethatobjectconvertedintomatrixURLparameters.Itwillbeeasiertounderstandthisbyexample,sobeginbypassinginsomedummydatatotheArticleListviewfromrouterLink:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<a[routerLink]="['']">

Default</a>

<a[routerLink]="['article',{listData:'foo'}]">

Article</a>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{}

Now,ifyouclickonthislink,youwillseeyourbrowsernavigatetothefollowingpathwhilestillsuccessfullyrenderingtheArticleListview:

/article;listData=foo

Toaccessthisdata,simplyextractitfromtheActivatedRouteparams:

[app/article-list.component.ts]

import{Component}from'@angular/core';

import{ActivatedRoute}from'@angular/router';

@Component({

template:`

<h3>ArticleList</h3>

<p*ngFor="letarticleIdofarticleIds">

<a[routerLink]="[articleId]">

Article{{articleId}}

</a>

</p>

`

})

exportclassArticleListComponent{

articleIds:Array<number>=[1,2,3,4,5];

constructor(privateactivatedRoute_:ActivatedRoute){

activatedRoute_.params

Page 418: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

.subscribe(params=>{

console.log('Listparams:');

console.log(window.location.href)

console.log(params);

});

}

}

Whentheviewisloaded,you'llseethefollowing:

Listparams:

/article;listData=foo

Object{listData:"foo"}

Awesome!Dothesameforthedetailview:

[app/article-list.component.ts]

import{Component}from'@angular/core';

import{ActivatedRoute}from'@angular/router';

@Component({

template:`

<h3>ArticleList</h3>

<p*ngFor="letarticleIdofarticleIds">

<a[routerLink]="[articleId,{detailData:'bar'}]">

Article{{articleId}}

</a>

</p>

`

})

exportclassArticleListComponent{

articleIds:Array<number>=[1,2,3,4,5];

constructor(privateactivatedRoute_:ActivatedRoute){

activatedRoute_.params

.subscribe(params=>{

console.log('Listparams:');

console.log(window.location.href)

console.log(params);

});

}

}

Addthesameamountofloggingtothedetailview:

[app/article-detail.component.ts]

import{Component}from'@angular/core';

import{ActivatedRoute}from'@angular/router';

@Component({

template:`

Page 419: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

<h3>ArticleDetail</h3>

<p>Showingarticle{{articleId}}</p>

<a[routerLink]="'../'">Backup</a>

`

})

exportclassArticleDetailComponent{

articleId:number;

constructor(privateactivatedRoute_:ActivatedRoute){

activatedRoute_.params

.subscribe(params=>{

console.log('Detailparams:');

console.log(window.location.href)

console.log(params);

this.articleId=params['articleId']

});

}

}

Whenyouvisitadetailpage,you'llseethefollowinglogged:

Detailparams:

/article;listData=foo/1;detailData=bar

Object{articleId:"1",detailData:"foo"}

Veryinteresting!NotonlyisAngularabletoassociatedifferentmatrixparameterswithdifferentroutinglevels,butithascombinedboththeexpectedarticleIdparameterandtheunexpecteddetailDataparameterintothesameObservableemission.

Page 420: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...AngularisabletoseamlesslyconvertfromaroutingarraycontainingamatrixparamobjecttoaserializedURLcontainingthematrixparams,thenbackintoadeserializedJavaScriptobjectcontainingtheparameterdata.ThisallowsyoutostorearbitrarydatainsideURLsatdifferentlevels,withouthavingtocramitallintoaquerystringattheend.

Page 421: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...NoticethatwhenyouclickonBackupinthedetailview,thelistDataURLparamispreserved.Angularwilldutifullymaintainthestateasyounavigatethroughouttheapplication,sousingmatrixparameterscanbeaveryeffectivewayofstoringstatefuldatathatsurvivesnavigationorpagereloads.

Page 422: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoNavigatingwithrouterLinksdemonstrateshowtonavigatearoundAngularapplicationsNavigatingwiththeRouterserviceusesanAngularservicetonavigatearoundanapplicationBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstateImplementingnestedviewswithrouteparametersandchildroutesgivesanexampleofhowtoconfigureAngularURLstosupportnestinganddatapassing

Page 423: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

AddingrouteauthenticationcontrolswithrouteguardsThenatureofsingle-pageapplicationswhollycontrollingtheprocessofroutingaffordsthemtheabilitytocontroleachstageoftheprocess.Foryou,thismeansthatyoucaninterceptroutechangesastheyhappenandmakedecisionsaboutwheretheusershouldgo.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6135/.

Page 424: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyInthisrecipe,you'llbuildasimplepseudo-authenticatedapplicationfromscratch.

Yougoalistoprotectusersfromcertainviewswhentheyarenotauthenticated,andatthesametime,implementasensiblelogin/logoutflow.

Page 425: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbydefiningtwoinitialviewswithroutesinyourapplication.OnewillbeaDefaultview,whichwillbevisibletoeverybody,andonewillbeaProfileview,whichwillbeonlyvisibletoauthenticatedusers:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ProfileComponent}from'./profile.component';

constappRoutes:Routes=[

{path:'profile',component:ProfileComponent},

{path:'**',component:DefaultComponent}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ProfileComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

[app/default.component.ts]

import{Component}from'@angular/core';

@Component({

template:`

<h2>Defaultview!</h2>

`

})

exportclassDefaultComponent{}

[app/profile.component.ts]

import{Component}from'@angular/core';

@Component({

template:`

Page 426: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

<h2>Profileview</h2>

Username:<input>

<button>Update</button>

`

})

exportclassProfileComponent{}

Obviously,thisdoesnotdoanythingyet.

ImplementingtheAuthservice

AsdoneintheObservableschapter,youwillimplementaservicethatwillmaintainthestateentirelywithinaBehaviorSubject.

Note

RecallthataBehaviorSubjectwillrebroadcastitslastemittedvaluewheneveranObserverissubscribedtoit.Thismeansitrequiressettingtheinitialstate,butforanauthenticationservicethisiseasy;itcanjuststartintheunauthenticatedstate.

Forthepurposeofthisrecipe,let'sassumethatausernameofnullmeanstheuserisnotauthenticatedandanyotherstringvaluemeanstheyareauthenticated:

[app/auth.service.ts]

import{Injectable}from'@angular/core';

import{BehaviorSubject}from'rxjs/BehaviorSubject';

import{Observable}from'rxjs/Observable';

@Injectable()

exportclassAuthService{

privateauthSubject_:BehaviorSubject<any>=

newBehaviorSubject(null);

usernameEmitter:Observable<string>;

constructor(){

this.usernameEmitter=this.authSubject_.asObservable();

this.logout();

}

login(username:string):void{

this.setAuthState_(username);

}

logout():void{

this.setAuthState_(null);

}

privatesetAuthState_(username:string):void{

this.authSubject_.next(username);

}

Page 427: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

Notethatnowherearewestoringtheusernameasastring.ThestateoftheauthenticationlivesentirelywithinBehaviorSubject.

Wiringuptheprofileview

Next,makethisserviceavailabletotheentireapplicationandwireuptheprofileview:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ProfileComponent}from'./profile.component';

import{AuthService}from'./auth.service';

constappRoutes:Routes=[

{path:'profile',component:ProfileComponent},

{path:'**',component:DefaultComponent}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ProfileComponent,

RootComponent

],

providers:[

AuthService

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

[app/profile.component.ts]

import{Component}from'@angular/core';

import{AuthService}from'./auth.service';

import{Observable}from'rxjs/Observable';

@Component({

template:`

<h2>Profileview</h2>

Username:<input#unvalue="{{username|async}}">

<button(click)=update(un.value)>Update</button>

Page 428: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

`

})

exportclassProfileComponent{

username:Observable<string>;

constructor(privateauthService_:AuthService){

this.username=authService_.usernameEmitter;

}

update(username:string):void{

this.authService_.login(username);

}

}

Tip

It'sveryhandytousetheasyncpipewheninterpolatingvalues.Recallthatwhenyouinvokesubscribe()onaserviceObservablefrominsideaninstantiatedviewcomponent,youmustinvokeunsubscribe()ontheSubscriptionwhenthecomponentisdestroyed;otherwise,yourapplicationwillhavealeakedlistener.MakingtheObservableavailabletotheviewsavesyouthistrouble!

Withtheprofileviewwiredup,addlinksandinterpolatetheusernameintotherootappviewinanavbar,togiveyourselftheabilitytonavigatearound.Youdon'thavetorevisitthefile;justaddallthelinksyou'llneedinthisrecipenow:

[app/root.component.ts]

import{Component}from'@angular/core';

import{Router}from'@angular/router';

import{AuthService}from'./auth.service';

import{Observable}from'rxjs/Observable';

@Component({

selector:'root',

template:`

<h3*ngIf="!!(username|async)">

Hello,{{username|async}}.

</h3>

<a[routerLink]="['']">Default</a>

<a[routerLink]="['profile']">Profile</a>

<a*ngIf="!!(username|async)"

[routerLink]="['login']">Login</a>

<a*ngIf="!!(username|async)"

[routerLink]="['logout']">Logout</a>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{

username:Observable<string>;

Page 429: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

constructor(privateauthService_:AuthService){

this.username=authService_.usernameEmitter;

}

}

Tip

Forconsistency,hereyouareusingtheasyncpipetomakethecomponentdefinitionsimpler.However,sinceyouhavefourinstancesinthetemplatereferencingthesameObservable,itmightbebetterdowntheroadtoinsteadsetonesubscribertoObservable,bindittoastringmemberinRootComponent,andinterpolatethisinstead.Angular'sdatabindingmakesthiseasyforyou,butyouwouldstillneedtoderegisterthesubscriberwhenthisisdestroyed.However,sinceitistheapplication'srootcomponent,youshouldn'treallyexpectthistohappen.

Restrictingrouteaccesswithrouteguards

Sofarsogood,butyouwillnoticethattheprofileviewisallowingtheusertoeffectivelyloginwilly-nilly.Youwouldinsteadliketorestrictaccesstothisviewandonlyallowtheusertovisititwhentheyarealreadyauthenticated.

Angulargivesyoutheabilitytoexecutecode,inspecttheroute,andredirectitasnecessarybeforethenavigationoccursusingaRouteGuard.

Note

Guardisabitofamisleadingtermhere.YoushouldthinkofthisfeatureasarouteshimthatletsyouaddlogicthatexecutesbeforeAngularactuallygoestothenewroute.Itcanindeed"Guard"aroutefromanunauthenticateduser,butitcanalsojustaseasilyconditionallyredirect,savethecurrentURL,orperformothertasks.

SincetheRouteGuardneedstohavethe@Injectabledecorator,itmakesgoodsensetotreatitasaservicetype.

StartoffwiththeskeletonAuthGuardServicedefinedinsideanewfileforrouteguards:

[app/route-guards.service.ts]

import{Injectable}from'@angular/core';

import{CanActivate}from'@angular/router';

@Injectable()

exportclassAuthGuardServiceimplementsCanActivate{

constructor(){}

canActivate(){

//Thismethodisinvokedduringroutechangesifthis

//classislistedintheRoutes

Page 430: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

}

Beforehavingthisdoanything,importthemoduleandaddittoRoutes:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ProfileComponent}from'./profile.component';

import{AuthService}from'./auth.service';

import{AuthGuardService}from'./route-guards.service';

constappRoutes:Routes=[

{

path:'profile',

component:ProfileComponent,

canActivate:[AuthGuardService]

},

{

path:'**',

component:DefaultComponent

}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

DefaultComponent,

ProfileComponent,

RootComponent

],

providers:[

AuthService,

AuthGuardService

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Now,eachtimetheapplicationmatchesaroutetoaprofileandtriestonavigatethere,thecanActivatemethoddefinedinsideAuthGuardServicewillbecalled.Thereturnvalueoftruemeansthenavigationcanoccur;thereturnvalueoffalsemeansthenavigationiscancelled.

Page 431: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Tip

canActivatecaneitherreturnabooleanoranObservable<boolean>.Beaware,shouldyoureturnObservable,theapplicationwilldutifullywaitfortheObservabletoemitavalueandcompleteitbeforenavigating.

Sincetheapplication'sauthenticationstatelivesinsideBehaviorSubject,allthismethodneedstodoissubscribe,checktheusername,andnavigateifitisnotnull.ItsuitsthistoreturnObservable<boolean>:

[app/route-guards.service.ts]

import{Injectable}from'@angular/core';

import{CanActivate,Router}from'@angular/router';

import{AuthService}from'./auth.service';

import{Observable}from'rxjs/Observable';

@Injectable()

exportclassAuthGuardServiceimplementsCanActivate{

constructor(privateauthService_:AuthService,

privaterouter_:Router){}

canActivate():Observable<boolean>{

returnthis.authService_.usernameEmitter.map(username=>{

if(!username){

this.router_.navigate(['login']);

}else{

returntrue;

}

});

}

}

Onceyouimplementthis,youwillnoticethatthenavigationwillneveroccur,eventhoughtheserviceisemittingtheusernamecorrectly.ThisisbecausetherecipientofthereturnvalueofcanActivateisn'tjustwaitingforanObservableemission;itiswaitingfortheObservabletocomplete.SinceyoujustwanttopeekattheusernamevalueinsideBehaviorSubject,youcanjustreturnanewObservablethatreturnsonevalueandtheniscompletedusingtake():

[app/route-guards.service.ts]

import{Injectable}from'@angular/core';

import{CanActivate,Router}from'@angular/router';

import{AuthService}from'./auth.service';

import{Observable}from'rxjs/Observable';

import'rxjs/add/operator/take';

@Injectable()

exportclassAuthGuardServiceimplementsCanActivate{

constructor(privateauthService_:AuthService,

privaterouter_:Router){}

Page 432: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

canActivate():Observable<Boolean>{

returnthis.authService_.usernameEmitter.map(username=>{

if(!username){

this.router_.navigate(['login']);

}else{

returntrue;

}

}).take(1);

}

}

Superb!However,thisapplicationstilllacksamethodtoformallyloginandlogout.

Addingloginbehavior

Sincetheloginpagewillneeditsownview,itshouldgetitsownrouteandcomponent.Oncetheuserlogsin,thereisnoneedtokeepthemontheloginpage,soyouwanttoredirectthemtothedefaultviewoncetheyaredone.

First,createthelogincomponentanditscorrespondingview:

[app/login.component.ts]

import{Component}from'@angular/core';

import{Router}from'@angular/router';

import{AuthService}from'./auth.service';

@Component({

template:`

<h2>Loginview</h2>

<input#un>

<button(click)="login(un.value)">Login</button>

`

})

exportclassLoginComponent{

constructor(privateauthService_:AuthService,

privaterouter_:Router){}

login(newUsername:string):void{

this.authService_.login(newUsername);

this.authService_.usernameEmitter

.subscribe(username=>{

if(!!username){

this.router_.navigate(['']);

}

});

}

}

[app/app.module.ts]

import{NgModule}from'@angular/core';

Page 433: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ProfileComponent}from'./profile.component';

import{LoginComponent}from'./login.component';

import{AuthService}from'./auth.service';

import{AuthGuardService}from'./route-guards.service';

constappRoutes:Routes=[

{

path:'login',

component:LoginComponent

},

{

path:'profile',

component:ProfileComponent,

canActivate:[AuthGuardService]

},

{

path:'**',

component:DefaultComponent

}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

LoginComponent,

DefaultComponent,

ProfileComponent,

RootComponent

],

providers:[

AuthService,

AuthGuardService

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Youshouldnowbeabletologin.Thisisallwellandgood,butyouwillnoticethatwiththisimplemented,updatingtheusernameintheprofileviewwillnavigatetothedefaultview,exhibitingthesamebehaviordefinedinthelogincomponent.ThisisbecausethesubscriberisstilllisteningtoAuthServiceObservable.YouneedtoaddinanOnDestroymethodtocorrectlyteardowntheloginview:

Page 434: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/login.component.ts]

import{Component,ngOnDestroy}from'@angular/core';

import{Router}from'@angular/router';

import{AuthService}from'./auth.service';

import{Subscription}from'rxjs/Subscription';

@Component({

template:`

<h2>Loginview</h2>

<input#un>

<button(click)="login(un.value)">Login</button>

`

})

exportclassLoginComponentimplementsOnDestroy{

privateusernameSubscription_:Subscription;

constructor(privateauthService_:AuthService,

privaterouter_:Router){}

login(newUsername:string):void{

this.authService_.login(newUsername);

this.usernameSubscription_=this.authService_

.usernameEmitter

.subscribe(username=>{

if(!!username){

this.router_.navigate(['']);

}

});

}

ngOnDestroy(){

//Onlyinvokeunsubscribe()ifthisexists

this.usernameSubscription_&&

this.usernameSubscription_.unsubscribe();

}

}

Addingthelogoutbehavior

Finally,youwanttoaddawayforuserstologout.Thiscanbeaccomplishedinanumberofways,butagoodimplementationwillbeabletodelegatethelogoutbehaviortoitsassociatedmethodswithoutintroducingtoomuchboilerplatecode.

Ideally,youwouldlikefortheapplicationtojustbeabletonavigatetothelogoutrouteandletAngularhandletherest.This,too,canbeaccomplishedwithcanActivate.First,defineanewRouteGuard:

[app/route-guards.service.ts]

import{Injectable}from'@angular/core';

import{CanActivate,Router}from'@angular/router';

Page 435: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{AuthService}from'./auth.service';

import{Observable}from'rxjs/Observable';

import'rxjs/add/operator/take';

@Injectable()

exportclassAuthGuardServiceimplementsCanActivate{

constructor(privateauthService_:AuthService,

privaterouter_:Router){}

canActivate():Observable<boolean>{

returnthis.authService_.usernameEmitter.map(username=>{

if(!username){

this.router_.navigate(['login']);

}else{

returntrue;

}

}).take(1);

}

}

@Injectable()

exportclassLogoutGuardServiceimplementsCanActivate{

constructor(privateauthService_:AuthService,

privaterouter_:Router){}

canActivate():boolean{

this.authService_.logout();

this.router_.navigate(['']);

returntrue;

}

}

Thisbehaviorshouldbeprettyself-explanatory.

Tip

YourcanActivatemethodmustmatchthesignaturedefinedintheCanActivateinterface,soeventhoughitwillalwaysnavigatetoanewview,youshouldaddareturnvaluetopleasethecompilerandtohandleanycaseswheretheprecedingcodeshouldfallthrough.

Next,addthelogoutcomponentandtheroute.Thelogoutcomponentwillneverberendered,buttheroutedefinitionrequiresthatitismappedtoavalidcomponent.SoLogoutComponentwillconsistofadummyclass:

[app/logout.component.ts}

import{Component}from'@angular/core';

@Component({

template:''

})

Page 436: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassLogoutComponent{}

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{DefaultComponent}from'./default.component';

import{ProfileComponent}from'./profile.component';

import{LoginComponent}from'./login.component';

import{LogoutComponent}from'./logout.component';

import{AuthService}from'./auth.service';

import{AuthGuardService,LogoutGuardService}

from'./route-guards.service';

constappRoutes:Routes=[

{

path:'login',

component:LoginComponent

},

{

path:'logout',

component:LogoutComponent,

canActivate:[LogoutGuardService]

},

{

path:'profile',

component:ProfileComponent,

canActivate:[AuthGuardService]

},

{

path:'**',

component:DefaultComponent

}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

LoginComponent,

LogoutComponent,

DefaultComponent,

ProfileComponent,

RootComponent

],

providers:[

AuthService,

AuthGuardService,

LogoutGuardService

],

Page 437: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Withthis,youshouldhaveafullyfunctionallogin/logoutbehaviorprocess.

Page 438: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...ThecoreofthisimplementationisbuiltaroundObservablesandRouteGuards.ObservablesallowyourAuthServicemoduletomaintainthestateandexposeitsimultaneouslythroughBehaviorSubject,andRouteGuardsallowyoutoconditionallynavigateandredirectatyourapplication'sdiscretion.

Page 439: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Applicationsecurityisabroadandinvolvedsubject.Therecipeshownhereinvolveshowtosmoothlymoveyouruseraroundtheapplication,butitisbynomeansarigoroussecuritymodel.

Theactualauthentication

Youshouldalwaysassumetheclientcanmanipulateitsownexecutionenvironment.Inthisexample,evenifyouprotectthelogin/logoutmethodsonAuthServiceaswellasyoucan,itwillbeeasyfortheusertogainaccesstothesemethodsandauthenticatethemselves.

Userinterfaces,whichAngularapplicationssquarelyfallinto,arenotmeanttobesecure.Securityresponsibilitiesfallontheserversideoftheclient/servermodelsincetheuserdoesnotcontrolthatexecutionenvironment.Inanactualapplication,thelogin()methodherewouldmakeanetworkrequestgetsomesortofatokenfromtheserver.Twoverypopularimplementations,JSONWebTokensandCookieauth,dothisindifferentways,buttheyareessentiallyvariationsofthesametheme.Angularorthebrowserwillstoreandsendthesetokens,butultimatelytheservershouldactasthegatekeeperofsecureinformation.

Securedataandviews

Anysecureinformationyoumightsendtotheclientshouldbebehindserver-basedauthentication.Formanydevelopers,thisisanobviousfact,especiallywhendealingwithanAPI.However,Angularalsorequeststemplatesandstaticfilesfromtheserver,andsomeoftheseyoumightnotwanttoservetothewrongpeople.Inthiscase,youwillneedtoconfigureyourservertoauthenticaterequestsforthesestaticfilesbeforeyouservethemtotheclient.

Page 440: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoNavigatingwiththeRouterserviceusesanAngularservicetonavigatearoundanapplicationBuildingstatefulRouterLinkbehaviorwithRouterLinkActiveshowshowtointegrateapplicationbehaviorwithaURLstateImplementingnestedviewswithrouteparametersandchildroutesgivesanexampleofhowtoconfigureAngularURLstosupportnestinganddatapassingWorkingwithmatrixURLparametersandroutingarraysdemonstratesAngular'sbuilt-inmatrixURLsupport

Page 441: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter7.Services,DependencyInjection,andNgModuleThischapterwillcoverthefollowingrecipes:

InjectingasimpleserviceintoacomponentControllingserviceinstancecreationandinjectionwithNgModuleServiceinjectionaliasingwithuseClassanduseExistingInjectingavalueasaservicewithuseValueandOpaqueTokensBuildingaprovider-configuredservicewithuseFactory

Page 442: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionAngular1gaveyouahodgepodgeofdifferentservicetypes.Manyofthemhadagreatdealofoverlap.Manyofthemwereconfusing.Andallofthemweresingletons.

Angular2hastotallythrownawaythisconcept.Initsplace,thereisashinynewdependencyinjectionsystemthatisfarmoreextensibleandsensiblethanitspredecessor.Itallowsyoutohaveatomicandnon-atomicservicetypes,aliasing,factories,andallkindsofincrediblyusefultoolsforuseinyourapplication.

Ifyouarelookingtouseservicesmuchinthesamewayasearlier,youwillfindthatyourunderstandingofservicetypeswilleasilycarryovertothenewsystem.Butfordeveloperswhowantmoreoutoftheirapplications,thenewworldofdependencyinjectionisincrediblypowerfulandobviouslybuiltforapplicationsthatcanscale.

Page 443: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

InjectingasimpleserviceintoacomponentThemostcommonusecasewillbeforacomponenttodirectlyinjectaserviceintoitself.Althoughtherhythmsofdefiningservicetypesandusingdependencyinjectionremainmostlythesame,it'simportanttogetagoodholdofthefundamentalsofAngular2'sdependencyinjectionschema,asitdiffersinseveralimportantways.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4263.

Page 444: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouhadthefollowingskeletonapplication:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>rootcomponent!</h1>

<button(click)="fillArticle()">Showarticle</button>

<h2>{{title}}</h2>

`

})

exportclassRootComponent{

title:string;

constructor(){}

fillArticle(){}

}

Yourobjectiveistoimplementaservicethatcanbeinjectedintothiscomponentandreturnanarticletitletofillthetemplate.

Page 445: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Asyoumightexpect,servicesinAngular2arerepresentedasclasses.Similartocomponents,servicesaredesignatedassuchwithan@Injectabledecorator.Createthisserviceinitsownfile:

[app/article.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassArticleService{

privatetitle_:string=`

CFOYodelsQuarterlyEarningsCall,StockSkyrockets

`

}

Thisservicehasaprivatetitlethatyouneedtotransfertothecomponent,butfirstyoumustmaketheserviceitselfavailabletothecomponent.Thiscanbedonebyimportingtheservice,thenlistingitintheproviderspropertyoftheapplicationmodule:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleService}from'./article.service';

@NgModule({

imports:[

BrowserModule

],

declarations:[

RootComponent

],

providers:[

ArticleService

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Nowthattheservicecanbeprovided,injectitintothecomponent:

[app/root.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

@Component({

Page 446: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

selector:'root',

template:`

<h1>rootcomponent!</h1>

<button(click)="fillArticle()">Showarticle</button>

<h2>{{title}}</h2>

`

})

exportclassRootComponent{

title:string;

constructor(privatearticleService_:ArticleService){}

fillArticle(){}

}

ThisnewcodewillcreateanewinstanceofArticleServicewhenRootComponentisinstantiated,andtheninjectitintotheconstructor.Anythinginjectedintoacomponentwillbeavailableasacomponentinstancemember,whichyoucanusetoconnectaservicemethodtoacomponentmethod:

[app/article.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassArticleService{

privatetitle_:string=`

CFOYodelsQuarterlyEarningsCall,StockSkyrockets

`;

getTitle(){

returnthis.title_;

}

}

[app/root.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

@Component({

selector:'root',

template:`

<h1>rootcomponent!</h1>

<button(click)="fillArticle()">Showarticle</button>

<h2>{{title}}</h2>

`

})

exportclassRootComponent{

title:string;

constructor(privatearticleService_:ArticleService){}

fillArticle():void{

Page 447: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

this.title=this.articleService_.getTitle();

}

}

Page 448: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Withoutthedecorator,theserviceyouhavejustbuiltisratherplainincomposition.Withthe@Injectable()decoration,theclassisdesignatedtotheAngularframeworkasonethatwillbeinjectedelsewhere.

Note

Designationasaninjectablehasanumberofconsiderationsthatareimportantlydistinctfromjustbeingpassedinparametrically.Whenistheinjectedclassinstantiated?Howisitlinkedtothecomponentinstance?Howareglobalandlocalinstancescontrolled?Thesearealldiscussedinthemoreadvancedrecipesinthischapter.

Designationasaninjectableserviceisonlyonepieceofthepuzzle.Thecomponentneedstobeinformedoftheexistenceoftheservice.Youmustfirstimporttheserviceclassintothecomponentmodule,butthisaloneisnotsufficient.Recallthatthesyntaxusedtoinjectaservicewassimplyawaytolistitasaconstructorparameter.Behindthescenes,Angularissmartenoughtorecognizethatthesecomponentargumentsaretobeinjected,butitrequiresthefinalpiecetoconnecttheimportedmoduletoitsplaceasaninjectedresource.

ThisfinalpiecetakestheformoftheproviderspropertyoftheNgModuledefinition.Forthepurposeofthisrecipe,itisn'timportantthatyouknowthedetailsoftheproperty.Inshort,thisarraydesignatesthearticleServiceconstructorparameterasaninjectableandidentifiesthatArticleServiceshouldbeinjectedintotheconstructor.

Page 449: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...It'simportanttoacknowledgeherehowtheTypeScriptdecoratorshelpthedependencyinjectionsetup.Decoratorsdonotmodifyaninstanceofaclass;rather,theymodifytheclassdefinition.TheNgModulecontainingtheproviderslistwillbeinitializedpriortoanyinstanceoftheactualcomponentbeinginstantiated.Thus,Angularwillbeawareofalltheservicesthatyoumightwanttoinjectintotheconstructor.

Page 450: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoControllingserviceinstancecreationandinjectionwithNgModulegivesabroadoverviewofhowAngular2architectsproviderhierarchiesusingmodules

Page 451: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ControllingserviceinstancecreationandinjectionwithNgModuleInastarkdeparturefromAngular1.x,Angular2featuresahierarchicalinjectionscheme.Thishasasubstantialnumberofimplications,andoneofthemoreprominentoneistheabilitytocontrolwhen,andhowmany,servicesarecreated.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/2102/.

Page 452: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeginwiththefollowingsimpleapplication:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>rootcomponent!</h1>

<article></article>

<article></article>

`

})

exportclassRootComponent{}

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<p>Articlecomponent!</p>

`

})

exportclassArticleComponent{}

[app/article.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassArticleService{

constructor(){

console.log('ArticleServiceconstructor!');

}

}

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component;

@NgModule({

imports:[

BrowserModule,

],

declarations:[

RootComponent,

ArticleComponent

Page 453: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

YourobjectiveistoinjectasingleinstanceofArticleServiceintothetwochildcomponents.Inthisrecipe,console.loginsidetheArticleServiceconstructorallowsyoutoseewhenoneisinstantiated.

Page 454: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...BeginbyimportingtheserviceintoAppModule,thenprovidingitwiththefollowing:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component;

import{ArticleService}from'./article.service;

@NgModule({

imports:[

BrowserModule,

],

declarations:[

RootComponent,

ArticleComponent

],

providers:[

ArticleService

]

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

SinceArticleServiceisprovidedinthesamemodulewhereArticleComponentisdeclared,youarenowabletoinjectArticleServiceintothechildArticleComponentinstances:

[app/article/article.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

@Component({

selector:'article',

template:`

<p>Articlecomponent!</p>

`

})

exportclassArticleComponent{

constructor(privatearticleService_:ArticleService){}

}

Withthis,youwillfindthatthesameserviceinstanceisinjectedintoboththechildcomponentsastheArticleServiceconstructor,namelyconsole.log,isonlyexecutedonce.

Page 455: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Splittinguptherootmodule

Astheapplicationgrows,itwillmakelessandlesssensetocrameverythingintothesametop-levelmodule.Instead,itwouldbeidealforyoutobreakapartmodulesintochunksthatmakesense.Inthecaseofthisrecipe,itwouldbepreferabletoprovideArticleServicetotheapplicationpiecesthatareactuallygoingtoinjectit.

DefineanewArticleModuleandmovetherelevantmoduleimportsintothatfileinstead:

[app/article.module.ts]

import{NgModule}from'@angular/core';

import{ArticleComponent}from'./article.component';

import{ArticleService}from'./article.service';

@NgModule({

declarations:[

ArticleComponent

],

providers:[

ArticleService

],

bootstrap:[

ArticleComponent

]

})

exportclassArticleModule{}

Then,importthisentiremoduleintoAppModuleinstead:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleModule}from'./article.module';

@NgModule({

imports:[

BrowserModule,

ArticleModule

],

declarations:[

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Page 456: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Ifyoustophere,you'llfindthattherearenoerrors,butAppModuleisn'tabletorenderArticleComponent.ThisisbecauseAngularmodules,likeothermodulesystems,needtoexplicitlydefinewhatisbeingexportedtoothermodules:

[app/article.module.ts]

import{NgModule}from'@angular/core';

import{ArticleComponent}from'./article.component';

import{ArticleService}from'./article.service';

@NgModule({

declarations:[

ArticleComponent

],

providers:[

ArticleService

],

bootstrap:[

ArticleComponent

],

exports:[

ArticleComponent

]

})

exportclassArticleModule{}

Withthis,youwillstillseethatArticleServiceisinstantiatedonce.

Page 457: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Angular2'sdependencyinjectiontakesadvantageofitshierarchystructurewhenprovidingandinjectingservices.Fromwhereaserviceisinjected,Angularwillinstantiateaservicewhereveritisprovided.Insideamoduledefinition,thiswillonlyeverhappenonce.

Inthiscase,youprovidedArticleServicetobothAppModuleandArticleModule.Eventhoughtheserviceisinjectedtwice(onceforeachArticleComponent),Angularusestheprovidersdeclarationtodecidewhentocreatetheservice.

Page 458: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Atthispoint,acuriousdevelopershouldhavelotsofquestionsabouthowexactlythisinjectionschemabehaves.Therearenumerousdifferentconfigurationflavorsthatcanbeusefultothedeveloper,andtheseconfigurationsonlyrequireaminorcodeadjustmentfromtheprecedingresult.

Injectingdifferentserviceinstancesintodifferentcomponents

Asyoumightanticipatefromtheprecedingexplanation,youcanreconfigurethisapplicationtoinjectadifferentArticleServiceinstanceintoeachchild,twointotal.ThiscanbedonebymigratingtheprovidersdeclarationoutofthemoduledefinitionandintotheArticleComponentdefinition:

[app/article.module.ts]

import{NgModule}from'@angular/core';

import{ArticleComponent}from'./article.component';

@NgModule({

declarations:[

ArticleComponent

],

bootstrap:[

ArticleComponent

],

exports:[

ArticleComponent

]

})

exportclassArticleModule{}

[app/article.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

@Component({

selector:'article',

template:`

<p>Articlecomponent!</p>

`,

providers:[

ArticleService

]

})

exportclassArticleComponent{

constructor(privatearticleService_:ArticleService){}

}

Youcanverifythattwoinstancesarebeingcreatedbyobservingthetwoconsole.logstatements

Page 459: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

calledfromtheArticleServiceconstructor.

Serviceinstantiation

Thelocationoftheprovidersalsomeansthatserviceinstanceinstantiationisboundtothelifetimeofthecomponent.Forthisapplication,thismeansthatwheneveracomponentiscreated,ifaserviceisprovidedinsidethatcomponentdefinition,anewserviceinstancewillbecreated.

Forexample,ifyouweretotoggletheexistenceofachildcomponentwithArticleServiceprovidedinsideit,itwillcreateanewArticleServiceeverytimeArticleComponentisconstructed:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>rootcomponent!</h1>

<button(click)="toggle=!toggle">Toggle</button>

<article></article>

<article*ngIf="toggle"></article>

`

})

exportclassRootComponent{}

YoucanverifythatnewinstancesarebeingcreatedeachtimengIfevaluatestotruebyobservingadditionalconsole.logstatementscalledfromtheArticleServiceconstructor.

Page 460: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoInjectingasimpleserviceintoacomponentwalksyouthroughthebasicsofAngular2'sdependencyinjectionschemaServiceinjectionaliasingwithuseClassanduseExistingdemonstrateshowtointerceptdependencyinjectionproviderrequests

Page 461: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ServiceinjectionaliasingwithuseClassanduseExistingAsyourapplicationbecomesmorecomplex,youmaycometoasituationwhereyouwouldliketouseyourservicesinapolymorphicstyle.Morespecifically,someplacesinyourapplicationmaywanttorequestServiceA,butaconfigurationsomewhereinyourapplicationwillactuallygiveitServiceB.Thisrecipewilldemonstrateonewayinwhichthiscanbeuseful,butthisbehaviorallowsyourapplicationtobemoreextensibleinmultipleways.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/1109/.

Page 462: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeginwiththefollowingskeletonapplication.

Dualservices

Youbeginwithtwoservices,ArticleServiceandEditorArticleService,andtheirsharedinterface,ArticleSourceInterface.EditorArticleServiceinheritsfromArticleService:

[app/article-source.interface.ts]

exportinterfaceArticleSourceInterface{

getArticle():Article

}

exportinterfaceArticle{

title:string,

body:string,

//?denotesanoptionalproperty

notes?:string

}

[app/article.service.ts]

import{Injectable}from'@angular/core';

import{Article,ArticleSourceInterface}

from'./article-source.interface';

@Injectable()

exportclassArticleServiceimplementsArticleSourceInterface{

privatetitle_:string=

"ResearchersDetermineHamSandwichNotTuringComplete";

privatebody_:string=

"Computersciencecommunityremainsskeptical";

getArticle():Article{

return{

title:this.title_,

body:this.body_

};

}

}

[app/editor-article.service.ts]

import{Injectable}from'@angular/core';

import{ArticleService}from'./article.service';

import{Article,ArticleSourceInterface}

from'./article-source.interface';

@Injectable()

exportclassEditorArticleServiceextendsArticleService

implementsArticleSourceInterface{

privatenotes_:string="Swingandamiss!";

Page 463: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

constructor(){

super();

}

getArticle():Article{

//Combineobjectsandreturnthejoinedobject

returnObject.assign(

{},

super.getArticle(),

{

notes:this.notes_

});

}

}

Aunifiedcomponent

Yourobjectiveistobeabletousethefollowingcomponentsothatboththeseservicescanbeinjectedintothefollowingcomponent:

[app/article.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

import{Article}from'./article-source.interface';

@Component({

selector:'article',

template:`

<h2>{{article.title}}</h2>

<p>{{article.body}}</p>

<p*ngIf="article.notes">

<i>Notes:{{article.notes}}</i>

</p>

`

})

exportclassArticleComponent{

article:Article;

constructor(privatearticleService_:ArticleService){

this.article=articleService.getArticle();

}

}

Page 464: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Whenlistingproviders,Angular2allowsyoutodeclareanaliasedreferencethatspecifieswhatserviceshouldactuallybeprovidedwhenoneofthecertaintypesisrequested.SinceAngular2injectionwillfollowthecomponenttreeupwardstofindtheprovider,onewaytodeclarethisaliasisbywrappingthecomponentwithaparentcomponentthatwillspecifythisalias:

[app/default-view.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

@Component({

selector:'default-view',

template:`

<h3>Defaultview</h3>

<ng-content></ng-content>

`,

providers:[ArticleService]

})

exportclassDefaultViewComponent{}

[app/editor-view.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

import{EditorArticleService}from'./editor-article.service';

@Component({

selector:'editor-view',

template:`

<h3>Editorview</h3>

<ng-content></ng-content>

`,

providers:[

{provide:ArticleService,useClass:EditorArticleService}

]

})

exportclassEditorViewComponent{}

Note

Notethatboththeseclassesareactingaspassthroughcomponents.Otherthanaddingaheader(whichismerelyforlearningthepurposeofinstructioninthisrecipe),theseclassesareonlyspecifyingaproviderandareunconcernedwiththeircontent.

Withthewrapperclassesdefined,youcannowaddthemtotheapplicationmodule,thenusethemtocreatetwoinstancesofArticleComponent:

[app/app.module.ts]

Page 465: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component';

import{DefaultViewComponent}from'./default-view.component';

import{EditorViewComponent}from'./editor-view.component';

import{ArticleComponent}from'./article.component';

import{ArticleService}from'./article.service';

import{EditorArticleService}from'./editor-article.service';

@NgModule({

imports:[

BrowserModule

],

declarations:[

RootComponent,

ArticleComponent,

DefaultViewComponent,

EditorViewComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<default-view>

<article></article>

</default-view>

<hr/>

<editor-view>

<article></article>

</editor-view>

`

})

exportclassRootComponent{}

Withthis,youshouldnowseethattheeditorversionofArticleComponentgetsthenotes,butthedefaultversiondoesnot.

Page 466: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...InAngular1,theservicetypethatwassupposedtobeinjectedwasidentifiedfromafunctionparameterbydoingadirectmatchoftheparametersymbol.function(Article)wouldinjecttheArticleservice,function(User)theUserservice,andsoon.Thisledtonastiness,suchastheminification-proofingofconstructorsbyprovidinganarrayofstringstomatchagainst['Article',function(Article){}].

Thisisnolongerthecase.Whenaproviderisregistered,theuseClassoptionutilizesthetwo-partdependencyinjectionmatchingschemeinAngular2.Thefirstpartistheprovidertoken,whichistheparametertypeoftheservicebeinginjected.Inthiscase,privatearticleService_:ArticleServiceusestheArticleServicetokentorequestthataninstancebeinjected.Angular2takesthisandmatchesthistokenagainstthedeclaredprovidersinthecomponenthierarchy.Whenatokenismatched,Angular2willusethesecondpart,theprovideritself,toinjectaninstanceoftheservice.

Inreality,providers:[ArticleService]isashorthandforproviders:[{provide:ArticleService,useClass:ArticleService}].Theshorthandisusefulsinceyouwillalmostalwaysberequestingtheserviceclassthatwouldmatchtheinjectedclass.However,inthisrecipe,youareconfiguringAngular2torecognizeanArticleServicetokenandsousetheEditorArticleServiceprovider.

Page 467: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...AnattentivedeveloperwillhaverealizedbythispointthattheutilityofuseClassislimitedinthesensethatitdoesnotallowyoutoindependentlycontrolwheretheactualserviceisprovided.Inotherwords,theplacewhereyouintercepttheproviderdefinitionwithuseClassisalsotheplacewherethereplacementclasswillbeprovided.

Inthisexample,useClassissuitablesinceyouareperfectlyhappytoprovideEditorArticleServiceinthesameplacewhereyouarespecifyingthatitshouldbeusedtoreplaceArticleService.However,itisnotdifficulttoimagineascenarioinwhichyouwouldliketospecifythereplacementservicetypebuthaveitinjectedhigherupinthecomponenttree.This,afterall,wouldallowyoutoreuseinstancesofaserviceinsteadofhavingtocreateanewoneforeachuseClassdeclaration.

Forthispurpose,youcanuseuseExisting.Itrequiresyoutoexplicitlyprovidetheservicetypeseparately,butitwillreusetheprovidedinstanceinsteadofcreatinganewone.Fortheapplicationyoujustcreated,youcannowreconfigureitwithuseExisting,andprovideboththeservicesattheRootComponentlevel.

Todemonstratethatyourreasoningabouttheservicebehavioriscorrect,doublethenumberofArticlecomponents,andaddalogstatementtotheconstructorofArticleServicetoensureyouareonlycreatingoneofeachservice:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<default-view>

<article></article>

</default-view>

<editor-view>

<article></article>

</editor-view>

<default-view>

<article></article>

</default-view>

<editor-view>

<article></article>

</editor-view>

`

})

exportclassRootComponent{}

[app/article.service.ts]

Page 468: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{Injectable}from'@angular/core';

import{Article,ArticleSourceInterface}from'./article-source.interface';

@Injectable()

exportclassArticleServiceimplementsArticleSourceInterface{

privatetitle_:string=

"ResearchersDetermineHamSandwichNotTuringComplete";

privatebody_:string=

"Computersciencecommunityremainsskeptical";

constructor(){

console.log('InstantiatedArticleService!');

}

getArticle():Article{

return{

title:this.title_,

body:this.body_

};

}

}

[app/editor-view.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

import{EditorArticleService}from'./editor-article.service';

@Component({

selector:'editor-view',

template:`

<h3>Editorview</h3>

<ng-content></ng-content>

`,

providers:[

{provide:ArticleService,useExisting:EditorArticleService}

]

})

exportclassEditorViewComponent{}

[app/default-view.component.ts]

import{Component}from'@angular/core';

import{ArticleService}from'./article.service';

@Component({

selector:'default-view',

template:`

<h3>Defaultview</h3>

<ng-content></ng-content>

`

//providersremoved

})

exportclassDefaultViewComponent{}

Page 469: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Inthisconfiguration,withuseClass,youwillseethatoneinstanceofArticleServiceandtwoinstancesofEditorArticleServicearecreated.WhenreplacedwithuseExisting,youwillfindthatonlyoneinstanceofeachiscreated.

Thus,inthisreconfiguredversionoftherecipe,yourapplicationisdoingthefollowing:

AttheRootComponentlevel,itisprovidingEditorArticleServiceAttheEditorViewComponentlevel,itisredirectingArticleServiceinjectiontokenstoEditorArticleService

AttheArticleComponentlevel,itisinjectingArticleServiceusingtheArticleServicetoken

Refactoringwithdirectiveproviders

Ifthisimplementationseemsclunkyandverbosetoyou,youarecertainlyontosomething.Theintermediatecomponentsareperformingtheirjobsquitewell,butaren'treallydoinganythingotherthanshimminginanintermediateprovider'sdeclaration.Insteadofwrappinginacomponent,youcanmigratetheprovider'sstatementintoadirectiveanddoawaywithboththeviewcomponents:

[app/root.component.ts]

import{Component}from'@angular/core';

import{ArticleComponent}from'./article.component';

import{ArticleService}from'./article.service';

import{EditorArticleService}from'./editor-article.service';

@Component({

selector:'root',

template:`

<article></article>

<articleeditor-view></article>

<article></article>

<articleeditor-view></article>

`

})

exportclassRootComponent{}

[app/editor-view.directive.ts]

import{Directive}from'@angular/core';

import{ArticleService}from'./article.service';

import{EditorArticleService}from'./editor-article.service';

@Directive({

selector:'[editor-view]',

providers:[

{provide:ArticleService,useExisting:EditorArticleService}

]

})

exportclassEditorViewDirective{}

Page 470: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component';

import{DefaultViewComponent}from'./default-view.component';

import{EditorViewDirective}from'./editor-view.directive';

import{ArticleComponent}from'./article.component';

import{ArticleService}from'./article.service';

import{EditorArticleService}from'./editor-article.service';

@NgModule({

imports:[

BrowserModule

],

declarations:[

RootComponent,

ArticleComponent,

DefaultViewComponent,

EditorViewDirective

],

providers:[

ArticleService,

EditorArticleService

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Yourapplicationshouldworkjustthesame!

Page 471: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoInjectingasimpleserviceintoacomponentwalksyouthroughthebasicsofAngular2'sdependencyinjectionschemaControllingserviceinstancecreationandinjectionwithNgModulegivesabroadoverviewofhowAngular2architectsproviderhierarchiesusingmodulesInjectingavalueasaservicewithuseValueandOpaqueTokensshowshowyoucanusedependency-injectedtokenstoinjectgenericobjectsBuildingaprovider-configuredservicewithuseFactorydetailstheprocessofsettingupaservicefactorytocreateconfigurableservicedefinitions

Page 472: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

InjectingavalueasaservicewithuseValueandOpaqueTokensInAngular1,therewasabroadselectionofservicetypesyoucoulduseinyourapplication.Asubsetofthesetypesallowedyoutoinjectastaticvalueinsteadofaserviceinstance,andthisusefulabilityiscontinuedinAngular2.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/3032/.

Page 473: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththefollowingsimpleapplication:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component';

@NgModule({

imports:[

BrowserModule

],

declarations:[

RootComponent,

ArticleComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<article></article>

`

})

exportclassRootComponent{}

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<imgsrc="{{logoUrl}}">

<h2>FoolandHisMoneyReunitedatLast</h2>

<p>Author:JakeHsu</p>

`

})

exportclassArticleComponent{}

Page 474: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Althoughaformalserviceclassdeclarationand@Injectabledecoratordesignationisnolongernecessaryforinjectingavalue,token/providermappingisstillneeded.Sincethereisnolongeraclassavailablethatcanbeusedtotypetheinjectable,somethingelsewillhavetoactasitsreplacement.

Angular2solvesthisproblemwithOpaqueToken.Thismoduleallowsyoutocreateaclasslesstokenthatcanbeusedtopairtheinjectedvaluewiththeconstructorargument.ThiscanbeusedalongsidetheuseValueprovideoption,whichsimplydirectlyprovideswhateveritscontentsareasinjectedvalues.

Defineatokenusingauniquestringinitsconstructor:

[app/logo-url.token.ts]

import{OpaqueToken}from'@angular/core';

exportconstLOGO_URL=newOpaqueToken('logo.url');

Incorporatethistokenintotheapplicationmoduledefinitionasyounormallywould.However,youmustspecifywhatitwillactuallypointtowhenitisinjected.Inthiscase,itshouldresolvetoanimageURLstring:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component';

import{LOGO_URL}from'./logo-url.token';

@NgModule({

imports:[

BrowserModule

],

declarations:[

RootComponent,

ArticleComponent

],

providers:[

{provide:LOGO_URL,useValue:

'https://angular.io/resources/images/logos/standard/logo-nav.png'}

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Page 475: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Finally,you'llbeabletoinjectthisintoacomponent.However,sinceyou'reinjectingsomethingthatwasn'tdefinedwiththe@Injectable()decoration,you'llneedtouse@Inject()insidetheconstructortotellAngularthatitshouldbeprovided,usingdependencyinjection.Furthermore,theinjectionwillnotattachitselftothecomponent'sthis,soyou'llneedtodothismanuallyaswell:

[app/article.component.ts]

import{Component,Inject}from'@angular/core';

import{LOGO_URL}from'./logo-url.token';

@Component({

selector:'article',

template:`

<imgsrc="{{logoUrl}}">

<h2>FoolandHisMoneyReunitedatLast</h2>

<p>Author:JakeHsu</p>

`

})

exportclassArticleComponent{

logoUrl:string;

constructor(@Inject(LOGO_URL)privatelogoUrl_){

this.logoUrl=logoUrl_;

}

}

Withthis,youshouldbeabletoseetheimagerenderedinyourbrowser!

Page 476: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...OpaqueTokenallowsyoutousenon-classtypesinsideAngular2'sclass-centricproviderschema.Itgeneratesasimpleclassinstancethatessentiallyisjustawrapperforthecustomstringyouprovided.Thisclassiswhatthedependencyinjectionframeworkwillusewhenattemptingtomapinjectiontokenstoproviderdeclarations.Thisgivesyoutheabilitytomorewidelyutilizedependencyinjectionthroughoutyourapplicationsinceyoucannowfeedanytypeofvaluewhereveraservicetypecanbeinjected.

Page 477: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Oneotherwayinwhichinjectingvaluesisusefulisthatitgivesyoutheabilitytostuboutservices.Supposeyouwantedtodefineadefaultstubservicethatshouldbeoverriddenwithanexplicitprovidertoenableusefulbehavior.Insuchacase,youcanimagineadefaultarticleentitythatcouldbedifferentlyconfiguredviaadirectivewhilereusingthesamecomponent:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<article></article>

<articleeditor-view></article>

`

})

exportclassRootComponent{}

[app/editor-article.service.ts]

import{Injectable}from'@angular/core';

exportconstMockEditorArticleService={

getArticle:()=>({

title:"Mocktitle",

body:"Mockbody"

})

};

@Injectable()

exportclassEditorArticleService{

privatetitle_:string=

"ProminentVeganEmbroiledinScrambledEggsScandal";

privatebody_:string=

"TofuFarmingAllianceretractedtheirendorsement.";

getArticle(){

return{

title:this.title_,

body:this.body_

};

}

}

[app/editor-view.directive.ts]

import{Directive}from'@angular/core';

import{EditorArticleService}from'./editor-article.service';

@Directive({

Page 478: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

selector:'[editor-view]',

providers:[EditorArticleService]

})

exportclassEditorViewDirective{}

[app/article.component.ts]

import{Component,Inject}from'@angular/core';

import{EditorArticleService}from'./editor-article.service';

@Component({

selector:'article',

template:`

<h2>{{title}}</h2>

<p>{{body}}</p>

`

})

exportclassArticleComponent{

title:string;

body:string;

constructor(privateeditorArticleService_:EditorArticleService){

letarticle=editorArticleService_.getArticle();

this.title=article.title;

this.body=article.body;

}

}

Withthis,yourArticleComponent,asdefinedintheprecedingcode,wouldusethemockservicewhenthedirectiveisnotattachedandtheactualservicewhenitisattached.

Page 479: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoControllingserviceinstancecreationandinjectionwithNgModulegivesabroadoverviewofhowAngular2architectsproviderhierarchiesusingmodulesServiceinjectionaliasingwithuseClassanduseExistingdemonstrateshowtointerceptdependencyinjectionproviderrequestsBuildingaprovider-configuredservicewithuseFactorydetailstheprocessofsettingupaservicefactorytocreateconfigurableservicedefinitions

Page 480: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Buildingaprovider-configuredservicewithuseFactoryOnefurtherextensionofdependencyinjectioninAngular2istheabilitytousefactorieswhendefiningyourproviderhierarchy.Aproviderfactoryallowsyoutoacceptinput,performarbitraryoperationstoconfiguretheprovider,andreturnthatproviderinstanceforinjection.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/0049/.

Page 481: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginagainwiththedualserviceandarticlecomponentsetupshowninServiceinjectionaliasingwithuseClassanduseExisting,earlierinthechapter.

Page 482: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...ProviderfactoriesinAngular2areexactlyasyoumightimaginetheywouldbe:functionsthatreturnaprovider.ThefactorycanbespecifiedinaseparatefileandreferencedwiththeuseFactoryprovideoption.

Beginbycombiningthetwoservicesintoasingleservice,whichwillbeconfiguredwithamethodcall:

[app/article.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassArticleService{

privatetitle_:string=

"FlyingSpaghettiMonsterSighted";

privatebody_:string=

"Adherentsinsistwearemissingthepoint";

privatenotes_:string="Spoton!";

privateeditorEnabled_:boolean=false;

getArticle():Object{

vararticle={

title:this.title_,

body:this.body_

};

if(this.editorEnabled_){

Object.assign(article,article,{

notes:this.notes_

});

}

returnarticle;

}

enableEditor():void{

this.editorEnabled_=true;

}

}

Definingthefactory

YourobjectiveistoconfigurethisservicetohaveenableEditor()invokedbasedonabooleanflag.Withproviderfactories,thisispossible.Definethefactoryinitsownfile:

[app/article.factory.ts]

import{ArticleService}from'./article.service';

exportfunctionarticleFactory(enableEditor?:boolean):ArticleService{

return(articleService:ArticleService)=>{

Page 483: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

if(enableEditor){

articleService.enableEditor();

}

returnarticleService;

}

}

InjectingOpaqueToken

Splendid!Next,you'llneedtoreconfigureArticleComponenttoinjectatokenratherthanthedesiredservice:

[app/article.token.ts]

import{OpaqueToken}from'@angular/core';

exportconstArticleToken=newOpaqueToken('app.article');

[app/article.component.ts]

import{Component,Inject}from'@angular/core';

import{ArticleToken}from'./article.token';

@Component({

selector:'article',

template:`

<h2>{{article.title}}</h2>

<p>{{article.body}}</p>

<p*ngIf="article.notes">

<i>Notes:{{article.notes}}</i>

</p>

`

})

exportclassArticleComponent{

article:Object;

constructor(@Inject(ArticleToken)privatearticleService_){

this.article=articleService_.getArticle();

}

}

CreatingproviderdirectiveswithuseFactory

Finally,you'llneedtodefinethedirectivesthatspecifyhowtousethisfactoryandincorporatethemintotheapplication:

[app/default-view.directive.ts]

import{Directive}from'@angular/core';

import{ArticleService}from'./article.service';

import{articleFactory}from'./article.factory';

import{ArticleToken}from'./article.token';

@Directive({

Page 484: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

selector:'[default-view]',

providers:[

{provide:ArticleToken,

useFactory:articleFactory(),

deps:[ArticleService]

}

]

})

exportclassDefaultViewDirective{}

[app/editor-view.directive.ts]

import{Directive}from'@angular/core';

import{ArticleService}from'./article.service';

import{articleFactory}from'./article.factory';

import{ArticleToken}from'./article.token';

@Directive({

selector:'[editor-view]',

providers:[

{

provide:ArticleToken,

useFactory:articleFactory(true),

deps:[ArticleService]

}

]

})

exportclassEditorViewDirective{}

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<articledefault-view></article>

<articleeditor-view></article>

`

})

exportclassRootComponent{}

Withthis,youshouldbeabletoseeboththeversionsofArticleComponent.

Page 485: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Thearticlecomponentisredefinedtouseatokeninsteadofaserviceinjection.Withthetoken,Angularwillwalkupthecomponenttreetofindwherethattokenisprovided.Thedirectivesdeclarethatthetokenismappedtoaproviderfactory,whichisamethodinvokedtoreturntheactualprovider.

useFactoryisthepropertythatmapstothefactoryfunction.depsisthepropertythatmapstotheservicedependenciesthatthefactoryhas.

Page 486: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Animportantdistinctionatthispointistorecognizethatallthesefactoryconfigurationsarehappeningbeforeanycomponentsareinstantiated.Theclassdecorationthatdefinestheproviderswillinvokethefactoryfunctiononsetup.

Page 487: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoControllingserviceinstancecreationandinjectionwithNgModulegivesabroadoverviewofhowAngular2architectsproviderhierarchiesusingmodulesServiceinjectionaliasingwithuseClassanduseExistingdemonstrateshowtointerceptdependencyinjectionproviderrequestsInjectingavalueasaservicewithuseValueandOpaqueTokensshowhowyoucanusedependencyinjectedtokenstoinjectgenericobjects

Page 488: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter8.ApplicationOrganizationandManagementThischapterwillcoverthefollowingrecipes:

Composingpackage.jsonforaminimumviableAngular2applicationConfiguringTypeScriptforaminimumviableAngular2applicationPerformingin-browsertranspilationwithSystemJSComposingapplicationfilesforaminimumviableAngular2applicationMigratingtheminimumviableAngular2applicationtoWebpackbundlingIncorporatingshimsandpolyfillsintoWebpackHTMLgenerationwithhtml-webpack-pluginSettingupanapplicationwithAngular'sCLI

Page 489: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionTheAngular2project'sambitionsgoalsinvolvetheutilizationofadifferentlanguagewithdifferentsyntaxandconstructs,aswellasprovidinghighefficiencyandmodularity.WhatthismeansforyouisthattheprocessofmaintaininganAngular2applicationmaybedifficult.

TheultimategoalistoefficientlyserveHTML,CSS,andJStoawebbrowserandtomakeiteasytodevelopthesourcecomponentsofthesestaticfiles.Howonearrivesatthisendpointcanbeworkedoutinanumberofdifferentways,anditwouldbeanexerciseinfutilitytowriteachapteronallofthem.

Instead,thischapterwillprovideafewopinionatedwaysofarrangingyourAngular2applicationinawaythatitwouldreflectthemostpopularandeffectivestrategies.ItwillalsoshowyouhowtobuildandextendaminimumviableAngular2application.Forsome,thiswillseemabitsimpleandrudimentary.However,themajorityofQuickstartprojectsorcodegenerationframeworkssimplygiveyouarepositoryandafewcommandstoruninordertogetoutofthedoor,andthesecommandsrunwithouttellingyouwhatthey'redoingorhowthey'redoingit!Inthischapter,youwilllearnhowtobuildanAngular2applicationfromthegroundupalongwiththepackagesandtoolsthatwillhelpyoudoitandwhythesemethodswereselected.

Page 490: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Composingpackage.jsonforaminimumviableAngular2applicationWhenthinkingaboutaminimumviableAngular2application,theconfigurationfilesareasclosetothemetaloftheruntimeenvironmentasyou'llget.Inthiscase,therearetwoconfigurationfilesthatwillcontrolhownpmanditsinstalledpackageswillmanagethefilesandthestart-upprocesses:package.jsonandtsconfig.json.

Somepartofthisrecipemaybeareviewfordevelopersthataremoreexperiencedwithnpmanditsfaculties.However,it'simportanttounderstandhowaverysimpleAngular2projectconfigurationcanbestructured,sothatyouareabletowhollyunderstandmorecomplexconfigurationsthatarebuilduponitsfundamentals.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/1332/.

Page 491: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyYou'llneedNode.jsinstalledforthisrecipetowork;you'llalsoneedanemptyprojectdirectory.Youshouldcreatethesetwoskeletonconfigurationfilesintherootprojectdirectory:

[package.json]

{

"name":"angular2-minimum-viable-application"

}

[tsconfig.json]

{

"compilerOptions":{

}

}

Tip

Foraquickandeasywaytoensureyouhavenpmsetupandreadytogo,usethefollowing:

npm--versionItshouldspitoutaversionnumberifeverythingissetupproperly.

Page 492: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...You'llstartwithpackage.json.Thepackage.jsonfileforaminimumviableapplicationcontainsthreesections:

dependencies:ThisisalistofpackagetargetsthattheproductionapplicationdirectlydependsupondevDependencies:Thisisalistofpackagetargetsthatthelocalenvironmentneedsforvariousreasons,suchascompilation,runningtests,orlintingscripts:Thesearecustom-definedcommand-lineutilitiesrunthroughnpm

package.jsondependencies

First,youneedtoaddinallthedependenciesthatyourapplicationwillneed.ThisincludesAngular2coremodules,whichliveinsidethenode_modules/@angulardirectory,aswellasahandfuloflibrarydependencies:

core-jsisthepolyfillfortheES6syntaxthattheTypeScriptcompilerdependsupon,suchasSet,Promise,andMap.reflect-metadataisthepolyfillfortheReflectMetadataAPI.ThisallowsyourTypeScripttousedecoratorsthatarenotpartofthestandardTypeScriptspecification,suchas@Component.rxjsisavailablefortheReactiveXJavaScriptobservableslibrary.Angular2nativelyusesObservables,andthisisadirectdependencyoftheframework.SystemJSisthedynamicmoduleloaderthatthisprojectneedsfortwopurposes:toimportandmapallthesourcefiles,andtobeabletoresolvetheES6import/exportdeclarations.zonejsistheZoneJSlibrarythatprovidesAngular2withtheabilitytouseasynchronousexecutioncontexts.Thisisadirectdependencyoftheframework.

Thisleavesyouwiththefollowing:

[package.json]

{

"name":"angular2-minimum-viable-application",

"dependencies":{

"@angular/common":"2.0.0",

"@angular/compiler":"2.0.0",

"@angular/core":"2.0.0",

"@angular/platform-browser":"2.0.0",

"@angular/platform-browser-dynamic":"2.0.0",

"core-js":"^2.4.1",

"reflect-metadata":"^0.1.3",

"rxjs":"5.0.0-beta.12",

"systemjs":"0.19.27",

"zone.js":"^0.6.23"

}

Page 493: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}

package.jsondevDependencies

Next,youneedtospecifythedevDependencies.

Note

Here'sannpmrefresher:devDependenciesaredependenciesthatarespecifictoadevelopmentenvironment.Buildscriptscanusethistodifferentiatebetweenpackagesthatneedtobeincludedinaproductionbundleandonesthatdon't.

lite-serveristhesimplefileserveryou'llusetotestthisapplicationlocally.Thiscouldbereplacedbyanynumberofsimplefileservers.typescriptistheTypeScriptcompiler.concurrentlyisasimplecommand-lineutilityforrunningsimultaneouscommandsfromannpmscript.

Thisleavesyouwiththefollowing:

[package.json]

{

"name":"angular2-minimum-viable-application",

"dependencies":{

"@angular/common":"2.0.0",

"@angular/compiler":"2.0.0",

"@angular/core":"2.0.0",

"@angular/platform-browser":"2.0.0",

"@angular/platform-browser-dynamic":"2.0.0",

"core-js":"^2.4.1",

"reflect-metadata":"^0.1.3",

"rxjs":"5.0.0-beta.12",

"systemjs":"0.19.27",

"zone.js":"^0.6.23"

},

"devDependencies":{

"concurrently":"^2.2.0",

"lite-server":"^2.2.2",

"typescript":"^2.0.2"

}

}

package.jsonscripts

Finally,youneedtocreatethescriptsthatyou'llusetogeneratecompiledfilesandrunthedevelopmentserver:

[package.json]

{

Page 494: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

"name":"angular2-minimum-viable-application",

"scripts":{

"lite":"lite-server",

"postinstall":"npminstall-S@types/node@types/core-js",

"start":"tsc&&concurrently'npmruntsc:w''npmrunlite'",

"tsc":"tsc",

"tsc:w":"tsc-w"

},

"dependencies":{

"@angular/common":"2.0.0",

"@angular/compiler":"2.0.0",

"@angular/core":"2.0.0",

"@angular/platform-browser":"2.0.0",

"@angular/platform-browser-dynamic":"2.0.0",

"core-js":"^2.4.1",

"reflect-metadata":"^0.1.3",

"rxjs":"5.0.0-beta.12",

"systemjs":"0.19.27",

"zone.js":"^0.6.23"

},

"devDependencies":{

"concurrently":"^2.2.0",

"lite-server":"^2.2.2",

"typescript":"^2.0.2"

}

}

Eachofthesescriptsservesapurpose,butmostyouwillnotneedtoinvokemanually.Hereisabriefdescriptionofeachofthesescripts:

litestartsoffaninstanceoflite-server.postinstallisthehookdefinitionthatwillrunafternpminstalliscompleted.Inthiscase,afternpmhasinstalledalltheprojectdependencies,youwanttoinstallthedeclarationfilesformodulesthatdonothavethem.npmrecognizesthepre-andpost-prefixesforscriptstrings.Anytimeascriptisrun,npmwillcheckforscriptswithpre-andpost-prefixingthemandrunthembeforeandafterthescript,respectively.Inthisrecipe,prelitewouldrunbeforelite,andpostlitewouldrunafterliteisrun.startisthedefinitionofthedefaultvalueofnpmstart.ThisscriptrunstheTypeScriptcompileroncetocompletion,thensimultaneouslyinvokestheTypeScriptcompilerwatcherandstartsupadevelopmentserver.Itisareservedscriptkeywordinnpm,thusthereisnoneedfornpmrunstart,althoughthatdoeswork.tsckicksofftheTypeScriptcompiler.TheTypeScriptcompilerreadsitssettingsfromthetsconfig.jsonthatexistsinthesamedirectory.tsc:wsetsafilewatchertorecompileuponfilechanges.

Page 495: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComposingpackage.jsonforaminimumviableAngular2applicationdescribeshowallthepiecesworkforthecorenodeprojectfileConfiguringTypeScriptforaminimumviableAngular2applicationtalksabouthowtoconfigurethecompilationtosupportanAngular2projectPerformingin-browsertranspilationwithSystemJSdemonstrateshowSystemJScanbeusedtoconnectuncompiledstaticfilestogetherComposingapplicationfilesforaminimumviableAngular2ApplicationwalksyouthroughhowtocreateanextremelysimpleAngular2appfromscratchMigratingtheminimumviableAngular2applicationtoWebpackbundlingdescribeshowtointegrateWebpackintoyourAngularapplicationbuildprocessIncorporatingshimsandpolyfillsintoWebpackgivesyouahandywayofmanagingAngular2polyfilldependenciesHTMLgenerationwithhtml-webpack-pluginshowsyouhowyoucanconfigureannpmpackagetoaddcompiledfilestoyourHTMLautomaticallySettingupanapplicationwithAngularCLIgivesadescriptionofhowtousetheCLI,whatitgivesyou,andwhattheseindividualpiecesdo

Page 496: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConfiguringTypeScriptforaminimumviableAngular2applicationInordertouseTypeScriptalongsideAngular2,therearetwomajorconsiderations:moduleinteroperabilityandcompilation.You'llneedtohandlebothinordertotakeyourapplication's.tsfiles,mixthemwithexternallibraryfiles,andoutputthefilesthatwouldbecompatiblewithyourtargetdevice.

TypeScriptcomesreadyasannpmpackage,butyouwillneedtotellithowtointeractwiththefilesandmodulesyou'vewritten,andwithfilesfromotherpackagesthatyouwanttouseinyourmodules.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/1053/.

Page 497: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyYoushouldfirstcompletetheinstructionsmentionedintheprecedingrecipe.ThiswillgiveyoutheframeworknecessarytodefineyourTypeScriptconfiguration.

Page 498: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...ToconfigureTypeScript,you'llneedtoadddeclarationfilestoincompatiblemodulesandgenerateaconfigurationfilethatwillspecifyhowthecompilershouldwork.

Declarationfiles

TypeScriptdeclarationfilesexisttospecifytheshapeofalibrary.Thesefilescanbeidentifiedbya.d.tssuffix.ThemajorityofnpmpackagesandotherJavaScriptlibrariesalreadyincludethesefilesinastandardizedlocation,sothatTypeScriptcanlocatethemandlearnabouthowthelibraryshouldbeinterpreted.Librariesthatdon'tincludetheseneedtobegiventhefiles,andfortunatelytheopensourcecommunityalreadyprovidesalotofthem.

Twolibrariesthatthisprojectusesdon'thavedeclarationfiles:nodeandcore-js.AsofTypeScript2.0,youareabletonativelyinstallthedeclarationfilesfortheselibrariesdirectlythroughnpm.The-Sflagisashorthandforsavingthemtopackage.json:

npminstall-S@types/node@types/core-js

Asensibleplaceforthisisinsidethepostinstallscript.

tsconfig.json

TheTypeScriptcompilerwilllookforthetsconfig.jsonfiletodeterminehowitshouldcompiletheTypeScriptfilesinthisdirectory.Thisconfigurationfileisn'trequired,asTypeScriptwillfallbacktothecompilerdefaults;however,youwanttomanageexactlyhowthe*.jsand*.map.jsfilesaregenerated.Modifythetsconfig.jsonfiletoappearasfollows:

[tsconfig.json]

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"moduleResolution":"node",

"emitDecoratorMetadata":true,

"experimentalDecorators":true,

"noImplicitAny":false

}

}

ThecompilerOptionsproperty,asyoumightexpect,specifiesthesettingsthecompilershouldusewhenthecompilingprocessfindsTypeScriptfiles.Intheabsenceofafilesproperty,TypeScriptwilltraversetheentireprojectdirectorystructuresearchingfor*.tsand*.tsxfiles.

AllthecompilerOptionspropertiescanbespecifiedequivalentlyascommand-lineflags,butdoingsointsconfig.jsonisamoreorganizedwayofgoingaboutyourproject.

Page 499: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

targetspecifiestheECMAScriptversionthatthecompilershouldoutput.Forbroadbrowsercompatibility,ES5isasensibledefaulthere.RecallthatECMAScriptisthespecificationuponwhichJavaScriptisbuilt.ThenewestfinishedspecificationisES6(alsocalledES2015),butmanybrowsersdonotfullysupportthisspecificationyet.TheTypeScriptcompilerwillcompileES6constructs,suchasclassandPromise,tonon-nativeimplementations.modulespecifieshowtheoutputfileswillhandlethemodulesintheoutputfiles.SinceyoucannotassumethatbrowsersareabletohandleES6modules,theTypeScriptcompilerwillhavetoconvertthemintoamodulesystemthatbrowsersareabletohandle.CommonJSisasensiblechoicehere.TheCommonJSmodulestyleinvolvesdefiningalltheexportsinasinglemoduleaspropertiesofasingle"exports"object.TheTypeScriptcompileralsosupportsAMDmodules(require.jsstyle),UMDmodules,SystemJSmodules,andofcourse,leavingthemodulesastheirexistingES6modulestyle.It'soutofthescopeofthisrecipetodivedeepintomodules.moduleResolutiondefineshowmodulepathswillberesolved.It'snotcriticalthatyouunderstandtheexactdetailsoftheresolutionstrategy,butthenodesettingwillgiveyoutheproperoutputformat.emitDecoratorMetadataandexperimentalDecoratorsenableTypeScripttohandleAngular2'suseofdecorators.Recalltheadditionofthereflect-metadatalibrarytosupportexperimentaldecorators.TheseflagsarethepointwhereitisabletotieintotheTypeScriptcompiler.noImplicitAnycontrolswhetherornotTypeScriptfilesmustbetyped.Whensettotrue,thiswillthrowanerrorifthereisanymissedtypinginyourproject.Thereisanongoingdiscussionregardingwhetherornotthisflagshouldbeset,asforcingobjectstobetypedisobviouslyusefultopreventerrorsthatmayarisefromambiguityincodebases.Ifyou'dliketoseeanexampleofthecompilerthrowinganerror,setnoImplicitAnytotrueandaddconstructor(foo){}insideAppComponent.Youshouldseethecompilercomplainaboutfoobeinguntyped.

Page 500: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...RunningthefollowingcommandwillstartuptheTypeScriptcompilerfromthecommandlineattherootlevelofyourprojectdirectory:

npmruntsc

Thecompilerwilllookfortsconfig.jsonifitisthereandfallbacktoitsdefaultsotherwise.Thesettingswithindirectthecompilerhowtohandleandvalidatethefiles,whichiswhereeverythingyoujustsetupcomesintoplay.

TheTypeScriptcompilerdoesn'trunthecodeormeaningfullyunderstandwhatitdoes,butitcandetectwhendifferentpiecesoftheapplicationaretryingtointeractinawaythatdoesn'tmakesense.The.d.tsdeclarationfileforamodulegivesTypeScriptawaytoinspecttheinterfacethatthemodulewillmakeavailableforconsumptionwhenitisimported.

Forexample,supposethatauthisanexternalmodulethatcontainsaUserclass.Thiswouldthenbeimportedviathefollowing:

import{User}from'./auth';

Byaddingthedeclarationfiletotheimportedmodule,TypeScriptisabletocheckthattheUserclassexists;italsobehavesinthewayyouareattemptingtointhelocalmodule.Ifitseesamismatch,itwillthrowanerroratcompilation.

Compilation

Dependingonyourframeworkexperience,thismaybesomethingyouhaveorhavenothadexperiencewithpreviously.Angular2(amongmanyframeworks)operatesunderthenotionthatJavaScript,asitcurrentlyexists,isinsufficientforwritinggoodcode.Thedefinitionof"good"hereissubjective,butallframeworksthatrequirecompilationwanttoextendormodifyJavaScriptinsomeformoranother.

However,allplatformsthattheseapplicationsneedtorunon—foryourpurposes,webbrowsers—onlyhaveaJavaScriptexecutionenvironmentthatexecutesfromuncompiledcode.Itisn'tfeasibleforyoutoextendhowthebrowserhandlespayloadsordeliversacompiledbinary,sothefilesthatyousendtotheclientmustplaybyitsrules.

TypeScript,bydefinitionanddesign,isastrictsupersetofES6,buttheseextensionscan'tbeusednativelyinabrowser.Eventoday,themajorityofbrowsersstilldonotfullysupportES6.Therefore,asensibleobjectiveistoconvertTypeScriptintoES5.1,whichistheECMAScriptstandardthatissupportedonallmodernbrowsers.Howyouarriveatthisoutputcanoccurinoneoftwoways:

SendtheTypeScripttotheclientasis.Therearein-browsercompilationlibrariesthatcan

Page 501: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

performacompilationontheclientandexecutetheresultingES5.1-compliantcodeasnormalJavaScript.Thismethodmakesdevelopmenteasiersinceyourbackenddoesn'tneedtodomuchotherthanservethefiles;however,itdeferscomputingtotheclient,whichdegradesperformanceandisthereforeconsideredabadpracticeforproductionapplications.CompiletheTypeScriptintoJavaScriptbeforesendingittotheclient.Theoverwhelmingmajorityofproductionapplicationswillelecttohandletheirbusinessthisway.EspeciallysincestaticfilesareoftenservedfromaCDNorstaticdirectory,itmakesgoodsensetocompileyourdescriptiveTypeScriptcodebaseintoJavaScriptfilesaspartofareleaseandthenservethosefilestotheclient.

Tip

WhenyoulookatthecompiledJavaScriptthatresultsfromcompilingTypeScript,itcanappearawfullybrutalandunreadable.Don'tworry!ThebrowserdoesnotcarehowmangledtheJavaScriptfilesareaslongastheycanbeexecuted.

Withthecompileroptionsyou'vespecifiedinthisrecipe,theTypeScriptcompilerwilloutputa.jsfileofthesamenamerightnexttoitssource,the.tsfile.

Page 502: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...BynomeansistheTypeScriptcompilerlimitedtoaone-off.tsfilegeneration.Ifoffersyouabroadrangeoftoolingfunctionsforspecifyingexactlyhowyouroutputfilesshouldappear.

Sourcemapgeneration

TheTypeScriptcompilerisalsocapableofgeneratingsourcemapstogoalongwithoutputfiles.Ifyou'renotfamiliarwiththem,theutilityofsourcemapsstemsfromthenatureofcompilationandminification:filesbeingdebuggedinthebrowsersarenotthefilesthatyouhavewritten.What'smore,whenusingacompiledTypeScript,thecompiledfileswon'tevenbeinthesamelanguage.

Sourcemapsareindexesthatpairwithcompiledfilestodescribehowtheyoriginallyappearedbeforetheywerecompiled.Morespecifically,the.js.mapfilescontainanencodingschemethatassociatesthecompiledand/orminifiedtokenswiththeiroriginalnameandstructureintheuncompiled.tsfile.Browsersthatunderstandhowtousesourcemapscanreconstructhowtheoriginalfileappearedandallowyoutosetbreakpoints,stepthrough,andinspectlexicalconstructsinsideitasifitweretheoriginal.

Sourcemapscanbespecifiedwithaspecialtokenaddedtothecompiledfile://#sourceMappingURL=/dist/example.js.map

Ifyouwanttogeneratesourcemapfilesfortheoutput,youcanspecifythisintheconfigurationfileaswellbyadding"sourceMap":true.Bydefault,the.js.mapfileswillbecreatedinthesameplaceastheoutput.jsfiles;alternatively,youcandirectthecompilertocreatethesourcemapsinsidethe.jsfileitself.

Tip

Eventhoughextraneousmapfileswon'taffecttheresultantapplicationbehavior,addingtheminlinemaybeundesirableifyoudon'twanttobloatyour.jspayloadsizeunnecessarily.Thisisbecauseclientsthatdon'twantorneedthemapfilescan'tdeclinetorequestthem.

Singlefilecompilation

SinceTypeScriptchecksallthelinkedmodulesagainsttheirimportsandexports,there'snoreasonyouneedtohaveallthecompiledfilesexistas1:1mappingstotheirinputfiles.TypeScriptisperfectlyhappytocombinethecompiledfilesintoasinglefileiftheoutputmoduleformatsupportsit.Specifythesinglefilewhereyouwishallthemodulestobecompiledwith"outFile":"/dist/bundle.js".

Note

Certainoutputmoduleformats,suchasCommonJS,won'tworkasconcatenatedmodulesina

Page 503: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

singlefile,sousingtheminconjunctionwithoutFilewillnotwork.AsoftheTypeScript1.8release,AMDandsystemoutputformatsaresupported.

IfyouplanonusingSystemJS,thiscompileroptioncanpotentiallyhelpyou,asSystemworkswithvirtuallyanymoduleformat.If,however,you'reusingaCommonJS-basedbundler,suchasWebpack,it'sbesttodelegatethefilecombinationtothebundler.

Page 504: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComposingpackage.jsonforaminimumviableAngular2applicationdescribeshowallthepiecesworkforthecorenodeprojectfilePerformingin-browsertranspilationwithSystemJSdemonstrateshowSystemJScanbeusedtoconnectuncompiledstaticfilestogetherComposingapplicationfilesforaminimumviableAngular2applicationwalksyouthroughhowtocreateanextremelysimpleAngular2appfromscratchMigratingtheminimumviableAngular2applicationtoWebpackbundlingdescribeshowtointegrateWebpackintoyourAngularapplicationbuildprocess

Page 505: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Performingin-browsertranspilationwithSystemJSItcanbeoftenusefultobeabletodeliverTypeScriptfilesdirectlytothebrowserandtodeferthetranspilationtoJavaScriptuntilthen.Whilethismethodhasperformancedrawbacks,itisextremelyusefulwhenprototypingandperformingexperimentations.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/2283/.

Page 506: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyCreateanemptyprojectdirectoryandcreatethefollowingpackage.jsoninsideit:

[package.json]

{

"scripts":{

"lite-server":"lite-server"

},

"devDependencies":{

"lite-server":"^2.2.2",

"systemjs":"^0.19.38",

"typescript":"^2.0.3"

}

}

Runningnpminstallshouldgetyoureadytowritecode.

Page 507: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TheTypeScriptnpmpackagecomesbundledwithatranspiler.WhencombinedwithSystemJSasthedesignatedtranspilationutility,thisallowsyoutoserveTypeScriptfilestotheclient;SystemJSwilltranspilethemintobrowser-compatibleJavaScript.

First,createtheindex.htmlfile.ThisfilewillimportthetworequiredJSlibraries:system.jsandtypescript.js.Next,itspecifiesthetypescriptasthedesiredtranspilerandimportsthetop-levelmain.tsfile:

[index.html]

<html>

<head>

<scriptsrc="node_modules/systemjs/dist/system.js">

</script>

<scriptsrc="node_modules/typescript/lib/typescript.js">

</script>

<script>

System.config({

transpiler:'typescript'

});

System.import('main.ts');

</script>

</head>

<body>

<h1id="text"></h1>

</body>

</html>

Next,createthetop-levelTypeScriptfile:

[main.ts]

import{article}from'./article.ts';

document.getElementById('text')

.innerHTML=article;

Finally,createthedependencyTypeScriptfile:

[article.ts]

exportconstarticle="Coolstory,bro";

Withthis,youshouldbeabletostartadevelopmentserverwithnpmrunlite-serverandseetheTypeScriptapplicationrunningnormallyinyourbrowseratlocalhost:3000.

Page 508: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...SystemJSisabletoresolvemoduledependenciesaswellasapplythetranspilertothemodulebeforeitreachesthebrowser.Ifyoulookatthetranspiledfilesinabrowserinspector,youcanseetheemittedfilesexistasvanillaJavaScriptIIFEs(instantaneouslyinvokedfunctionexpressions)aswellastheircoupledsourcemaps.Withthesetools,itispossibletobuildasurprisinglycomplexapplicationwithoutanysortofbackendfilemanagement.

Page 509: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Unlessyou'reexperimentingordoingaroughproject,doingtranspilationinthebrowserisn'tpreferred.Anycomputationyoucandoontheservershouldbedonewheneverpossible.Additionally,alltheclientstranspilingtheirownfilesallperformhighlyredundantoperationssinceallofthemtranspilethesamefiles.

Page 510: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComposingpackage.jsonforaminimumviableAngular2applicationdescribeshowallthepiecesworkforthecorenodeprojectfileConfiguringTypeScriptforaminimumviableAngular2applicationtalksabouthowtoconfigurethecompilationtosupportanAngular2projectComposingapplicationfilesforaminimumviableAngular2applicationwalksyouthroughhowtocreateanextremelysimpleAngular2appfromscratchMigratingtheminimumviableAngular2applicationtoWebpackbundlingdescribeshowtointegrateWebpackintoyourAngularapplicationbuildprocessIncorporatingshimsandpolyfillsintowebpackgivesyouahandywayofmanagingAngular2polyfilldependenciesHTMLgenerationwithhtml-webpack-pluginshowsyouhowyoucanconfigureannpmpackagetoaddcompiledfilestoyourHTMLautomaticallySettingupanapplicationwithAngularCLIgivesadescriptionofhowtousetheCLI,whatitgivesyou,andwhattheseindividualpiecesdo

Page 511: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ComposingapplicationfilesforaminimumviableAngular2applicationWhenapproachingAngular2initially,itisusefultohaveanunderstandingofanapplicationstructurethatistorndowntothebaremetal.Inthecaseofaminimumviableapplication,itwillconsistofasinglecomponent.Sincethisisachapteronapplicationorganization,itisn'tsomuchaboutwhatthatcomponentwilllooklike,butratherhowtotaketheTypeScriptcomponentdefinitionandactuallygetittorenderinawebpage.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/6323/.

Page 512: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyThisrecipeassumesyouhavecompletedallthestepsgivenintheComposingconfigurationfilesforaminimumviableAngular2applicationrecipe.Thenpmmoduleinstallationshouldsucceedwithnoerrors:

npminstall

Page 513: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Thesimplestplacetostartisthecoreapplicationcomponent.

app.component.ts

Implementacomponentinsideanewapp/directoryasfollows;thereshouldbenosurprises:

[app/app.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-root',

template:'<h1>AppComponenttemplate!</h1>'

})

exportclassAppComponent{}

Thisisaboutassimpleacomponentcanpossiblyget.Oncethisissuccessfullyrenderedintheclient,thiscomponentshouldjustbeabiglineoftext.

app.module.ts

Next,youneedtodefinetheNgModulethatwillbeassociatedwiththiscomponent.Createanotherfileintheapp/directory,app.module.ts,andhaveitmatchthefollowing:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{AppComponent}from'./app.component';

@NgModule({

imports:[BrowserModule],

declarations:[AppComponent],

bootstrap:[AppComponent]

})

exportclassAppModule{}

There'sabitmoregoingonhere:

importsspecifiesthemoduleswhoseexporteddirectives/pipesshouldbeavailabletothismodule.

Tip

ImportingBrowserModulegivesyouaccesstocoredirectivessuchasNgIfandalsospecifiesthetypeofrenderer,eventmanagement,anddocumenttype.Ifyourapplicationisrenderinginawebbrowser,thismodulegivesyouthetoolsyouneedtodothis.

Page 514: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

declarationsspecifieswhichdirectives/pipesarebeingexportedbythismodule.Inthiscase,AppComponentisthesoleexport.bootstrapspecifieswhichcomponentsshouldbebootstrappedwhenthismoduleisbootstrapped.Morespecifically,componentslistedherewillbedesignatedforrenderingwithinthismodule.AppComponentneedstobebootstrappedandrenderedsomewhere,andthisiswherethisspecificationwilloccur.

Thiscompletesthemoduledefinition.Atthispoint,youhavesuccessfullylinkedthecomponenttoitsmodule,butthismoduleisn'tbeingbootstrappedanywhereorevenincluded.

main.ts

You'llchangethisnextwithmain.ts,thetop-levelTypeScriptfile:

[app/main.ts]

import{platformBrowserDynamic}

from'@angular/platform-browser-dynamic';

import{AppModule}from'./app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

ThisfiledefinestheNgModuledecoratorthatwillbeusedforAppComponent.Insideit,youspecifythatthemodulemustimportBrowserModule.

Note

RecallthatAngular2isdesignedtobeplatform-independent.Morespecifically,itstrivestoallowyoutowritecodethatmightnotnecessarilyrunonaconventionalwebbrowser.Inthiscase,youaretargetingastandardwebbrowser,soimportingBrowserModulefromtheplatformBrowsertargetisthewayinwhichyoucaninformtheapplicationofthis.Ifyouweretargetingaseparateplatform,youwouldselectadifferentplatformtoimportintoyourrootapplicationcomponent.

ThisNgModuledeclarationalsospecifiesthatAppComponentexistsandshouldbebootstrapped.

Note

BootstrappingishowyoukickoffyourAngular2application,butithasaveryspecificdefinition.Invokingbootstrap()tellsAngulartomountthespecifiedapplicationcomponentontoDOMelementsidentifiedbythecomponent'sselector.Thiskicksofftheinitialroundofchangedetectionanditssideeffects,whichwillcompletethecomponentinitialization.

Sinceyou'vedeclaredthatthismodulewillbootstrapAppComponentwhenitisbootstrapped,thismodulewillinturnbetheonebootstrappedfromthetop-levelTypeScriptfile.Angular2pushesforthisconventionasamain.tsfile:

Page 515: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/main.ts]

import{platformBrowserDynamic}

from'@angular/platform-browser-dynamic';

import{AppModule}from'./app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

TheplatformBrowserDynamicmethodreturnsaplatformobjectthatexposesthebootstrapModulemethod.ItconfiguresyourapplicationtobebootstrappedwithAngular2'sjust-in-time(JIT)compiler.

Tip

Fornow,thedetailsofwhyyouarespecifyingjust-in-timecompilationaren'timportant.It'senoughtoknowthatJITcompilationisasimplerversion(asopposedtoahead-of-timecompilation)inAngular2'sofferings.

index.html

Finally,youneedtobuildanHTMLfilethatiscapableofbundlingtogetherallthesecompiledfilesandkickingofftheapplicationinitialization.Beginwiththefollowing:

[index.html]

<html>

<head>

<title>Angular2MinimumViableApplication</title>

<scriptsrc="node_modules/zone.js/dist/zone.js">

</script>

<scriptsrc="node_modules/reflect-metadata/Reflect.js">

</script>

<scriptsrc="node_modules/systemjs/dist/system.src.js">

</script>

</head>

<body>

<app-root></app-root>

</body>

</html>

Mostofthissofarshouldbeexpected.ZoneJSandReflectareAngular2dependencies.Themoduleloaderyou'lluseisSystemJS.<app-root>istheelementthatAppComponentwillrenderinside.

ConfiguringSystemJS

Next,SystemJSneedstobeconfiguredtounderstandhowtoimportmodulefilesandhowtoconnectmodulesfrombeingimportedinsideothermodules.Inotherwords,itneedstobegivenafiletobeginwithandadirectoryofmappingsfordependenciesofthatmainfile.Thiscanbe

Page 516: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

accomplishedwithSystem.config()andSystem.import(),whicharemethodsexposedontheglobalSystemobject:

[index.html]

<html>

<head>

<title>Angular2MinimumViableApplication</title>

<scriptsrc="node_modules/zone.js/dist/zone.js">

</script>

<scriptsrc="node_modules/reflect-metadata/Reflect.js">

</script>

<scriptsrc="node_modules/systemjs/dist/system.src.js">

</script>

<script>

System.config({

paths:{

'ng:':'node_modules/@angular/'

},

map:{

'@angular/core':'ng:core/bundles/core.umd.js',

'@angular/common':'ng:common/bundles/common.umd.js',

'@angular/compiler':

'ng:compiler/bundles/compiler.umd.js',

'@angular/platform-browser':

'ng:platform-browser/bundles/platform-browser.umd.js',

'@angular/platform-browser-dynamic':

'ng:platform-browser-dynamic/bundles/platform-browser-

dynamic.umd.js',

'rxjs':'node_modules/rxjs'

},

packages:{

app:{

main:'./main.js'

},

rxjs:{

defaultExtension:'js'

}

}

});

System.import('app');

</script>

</head>

<body>

<app-root></app-root>

</body>

</html>

System.config()specifieshowSystemJSshouldhandlethefilespassedtoit.

Thepathspropertyspecifiesanaliastoshortenthepath'sinsidemap.Itactsasasimple

Page 517: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

findandreplacefunction,soanyfoundinstancesofng:arereplacedwithnode_modules/@angular/.ThemappropertyspecifieshowSystemJSshouldresolvethemoduleimportsthatyouhavenotexplicitlydefined.Here,thistakestheformoffivecoreAngularmodulesandtheRxJSlibrary.Thepackagespropertyspecifiesthetargetsthatwillbeimportedbythispropertyandthefilestheyneedtomapto.

Tip

Forexample,theapppropertywillbeusedwhenamoduleimportsapp,andinsideSystemJS,thiswillmaptomain.js.Similarly,whenamodulerequiresanRxJSmodule,suchasSubject,SystemJSwilltaketherxjs/Subjectimportpath,recognizethatdefaultExtensionisspecifiedasjs,mapthemoduletoitsfilerepresentationnode_modules/rxjs/Subject.js,andimportit.

Page 518: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComposingpackage.jsonforaminimumviableAngular2applicationdescribeshowallthepiecesworkforthecorenodeprojectfileConfiguringTypeScriptforaminimumviableAngular2applicationtalksabouthowtoconfigurecompilationtosupportanAngular2projectPerformingin-browsertranspilationwithSystemJSdemonstrateshowSystemJScanbeusedtoconnectuncompiledstaticfilestogetherMigratingtheminimumviableAngular2applicationtoWebpackbundlingdescribeshowtointegrateWebpackintoyourAngularapplicationbuildprocessIncorporatingshimsandpolyfillsintoWebpackgivesyouahandywayofmanagingAngular2polyfilldependenciesHTMLgenerationwithhtml-webpack-pluginshowsyouhowyoucanconfigureannpmpackagetoaddcompiledfilestoyourHTMLautomaticallySettingupanapplicationwithAngularCLIgivesadescriptionofhowtousetheCLI,whatitgivesyou,andwhattheseindividualpiecesdo

Page 519: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

MigratingtheminimumviableapplicationtoWebpackbundlingItisadvantageousformanyreasonstomakeitaseasyandquickaspossiblefortheclienttoloadandrunthecodesentfromyourserver.Oneoftheeasiestandmosteffectivewaysofdoingthisisbybundlinglotsofcodeintoasinglefile.Innearlyallcases,itishighlyefficientforthebrowsertoloadasinglefilethatcontainsallthedependenciesrequiredtobootstrapanapplication.

WebpackoffersmanyusefultoolsandamongthemistheterrificJSbundler.Thisrecipedemonstrateshowyouwillbeabletocombineyourentireapplication(includingnpmpackagedependencies)intoasingleJavaScriptfilethatthebrowserwillbeserved.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/3310/.

Page 520: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyYoushouldhavecompletedallthestepsgivenintheComposingconfigurationfilesforaminimumviableAngular2applicationandComposingapplicationfilesforaminimumviableAngular2applicationrecipes.npmstartshouldstartupthedevelopmentserver,anditshouldbevisibleatlocalhost:3000.

Page 521: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbyremovingtheapplication'sdependencyonSystemJS.webpackisabletoresolvedependenciesandbundleallyourfilesintoasingleJSfile.Beginbyinstallingwebpackwiththeglobalflag:

npminstallwebpack-g

webpack.config.js

webpacklooksforawebpack.config.jsfileforinstructionsonhowtobehave.Createthisnow:

[webpack.config.js]

module.exports={

entry:"./app/main.js",

output:{

path:"./dist",

filename:"bundle.js"

}

};

Nothingexceptionallycomplicatedisgoingonhere.Thistellswebpacktoselectmain.jsasthetop-levelapplicationfile,resolveallitsdependenciestothefilesthatdefinethem,andbundlethemintoasinglebundle.jsinsideadist/directory.

Tip

Atthispoint,youcancheckthatthisisworkingbyinvokingwebpackfromthecommandline,whichwillrunthebundler.Youshouldseebundle.jsappearinsidedist/withallthemoduledependenciesinsideit.

Thisisagoodstart,butthisgeneratedfilestillisn'tbeingusedanywhere.Next,you'llmodifyindex.htmltousethefile:

[index.html]

<html>

<head>

<title>Angular2MinimumViableApplication</title>

<scriptsrc="node_modules/zone.js/dist/zone.js">

</script>

<scriptsrc="node_modules/reflect-metadata/Reflect.js">

</script>

<scriptsrc="dist/bundle.js">

</script>

</head>

<body>

Page 522: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

<app-root></app-root>

</body>

</html>

Probablynotwhatyouwereexpectingatall!Sincebundle.jsistheapplicationentrypointandSystemJSisnolongerneededtoresolveanymodules(becausewebpackisalreadydoingthisforyouwhenbundlingthefiles),youcanremovetheapplication'sdependencyonSystemJS.

Sincethisisthecase,youcanremovetheSystemdependencyfromyourpackage.jsonandaddthewebpackscriptsanddependency:

[package.json]

{

"name":"mva-bundling",

"scripts":{

"start":"tsc&&webpack&&concurrently'npmruntsc:w'

'npmrunwp:w''npmrunlite'",

"lite":"lite-server",

"postinstall":"npminstall-S@types/node@types/core-js",

"tsc":"tsc",

"tsc:w":"tsc-w",

"wp":"webpack",

"wp:w":"webpack--watch"

},

"dependencies":{

"@angular/common":"2.0.0",

"@angular/compiler":"2.0.0",

"@angular/core":"2.0.0",

"@angular/platform-browser":"2.0.0",

"@angular/platform-browser-dynamic":"2.0.0",

"core-js":"^2.4.1",

"reflect-metadata":"^0.1.3",

"rxjs":"5.0.0-beta.12",

"zone.js":"^0.6.23"

},

"devDependencies":{

"concurrently":"^2.2.0",

"lite-server":"^2.2.2",

"typescript":"^2.0.2",

"webpack":"^1.13.2"

}

}

WhetherornotwebpackandtypescriptbelongtodevDependencieshereisamatterofdisputeandislargelysubjecttohowyoumanageyourlocalenvironment.Ifyou'vealreadyinstalledthemwiththeglobalflag,thenyoudon'tneedtolistithereasadependency.Thisisbecausenpmwillsearchforgloballyinstalledpackagesandfindthemforyoutorunnpmscripts.Furthermore,listingitherewillinstalladuplicatewebpacklocaltothisproject,whichisobviouslyredundant.

Page 523: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Forthepurposeofthisrecipe,itishelpfultohaveithere.Thisisbecauseyoucanensurethatasinglenpminstallonthecommandlinewillfetchallthepackagesyouneedoffthebat,andthiswillletyouspecifytheversionyouwantwithintheproject.

Now,whenyouexecutenpmstart,thefollowingoccurs:

TypeScriptdoesaninitialcompilationof.tsfilesinto.jsfiles.WebpackdoesaninitialbundlingofalltheJSfilesintoasinglebundle.jsinthedist/directory.Simultaneously,lite-serverisstarted,theTypeScriptcompilerwatcherisstarted,andtheWebpackwatcherisstarted.Upona.tsfilechange,TypeScriptwillcompileitintoa.jsfile,andWebpackwillpickupthatfilechangeandrebundleitintobundle.js.Thelite-serverwillseethatbundle.jsischangedandreloadthepage,soyoucanseethechangesbeingupdatedautomatically.

Tip

Withoutspecifyingtheconfigurationsmoreclosely,theTypeScript,Webpack,andthelite-serverfilewatchlistswillusetheirdefaultsettings,whichmaybetoobroadandthereforewouldwatchfilestheydonotcareabout.Ideally,TypeScriptwouldonlywatch.tsfiles(whichdoesthiswithyourtsconfig.json),Webpackwouldonlywatch.html,.js,and.cssfiles,andlite-serverwouldonlywatchthefilesitactuallyservestotheclient.

Page 524: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoIncorporatingshimsandpolyfillsintoWebpackgivesyouahandywayofmanagingAngular2polyfilldependenciesHTMLgenerationwithhtml-webpack-pluginshowsyouhowyoucanconfigureannpmpackagetoaddcompiledfilestoyourHTMLautomatically

Page 525: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IncorporatingshimsandpolyfillsintoWebpackSofar,thishasbeenamuchcleanerimplementation,butyoustillhavethetwodanglingshimsinsidetheindex.htmlfile.You'vepareddownindex.htmlsuchthatitisnowrequestingonlyahandfulofJSfilesinsteadofeachmoduletargetindividually,butyoucangoevenfurtherandbundlealltheJSfilesintoasinglefile.

Thechallengeinthisisthatbrowsershimsaren'tdeliveredviamodules;inotherwords,therearen'tanyotherfilesthatwillimportthesetousethem.Theyjustassumetheiruseisavailable.Therefore,thestandardWebpackbundlingwon'tpickupthesetargetsandincludetheminthebundledfile.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/7479/.

Page 526: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyYoushouldcompletetheMigratingtheminimumviableapplicationtoWebpackbundlingrecipefirst,whichwillgiveyouallthesourcefilesneededforthisrecipe.

Page 527: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Thereareanumberofwaystogoaboutdoingthis,includingsomethatinvolvetheadditionofWebpackplugins,butthere'sanextremelysimplewayaswell:justaddtheimportsmanually.

Createanewpolyfills.ts:

[src/polyfills.ts]

import"reflect-metadata";

import"zone.js";

Importthismodulefrommain.ts:

[src/main.ts]

import'./polyfills';

import{platformBrowserDynamic}

from'@angular/platform-browser-dynamic';

import{AppModule}from'./app/app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Finally,cleanupindex.html:

[index.html]

<html>

<head>

<title>Angular2MinimumViableApplication</title>

<body>

<app-root></app-root>

<scriptsrc="dist/bundle.js"></script>

</body>

</html>

Now,Webpackshouldbeabletoresolvetheshimimports,andalltheneededfileswillbeincludedinsidebundle.js.

Page 528: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...TheonlyreasonthatthepolyfillsarenotdiscoveredbyWebpackisbecausetheyarenotrequiredanywhereintheapplication.Rather,anywheretheyareusedleadstotheassumptionthattheexposedtargets,suchasZone,havepreviouslybeenmadeavailable.Therefore,itiseasyforyoutosimplyimportthemattheverytopofyourapplication,whichhasawell-definedpointinthecode.WiththisWebpack,youwillbeabletodiscovertheexistenceofpolyfillsandincorporatethemintothegeneratedbundle.

Page 529: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoMigratingtheminimumviableAngular2applicationtoWebpackbundlingdescribeshowtointegrateWebpackintoyourAngularapplicationbuildprocessHTMLgenerationwithhtml-webpack-pluginshowsyouhowyoucanconfigureannpmpackagetoaddcompiledfilestoyourHTMLautomatically

Page 530: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

HTMLgenerationwithhtml-webpack-pluginIdeally,youwouldliketobeabletohaveWebpackmanagethebundledfileanditsinjectionintothetemplate.Bydefault,Webpackisunabletodothis,asitisonlyconcernedwiththefilesrelatedtoscripting.Fortunately,WebpackoffersanextremelypopularpluginthatallowsyoutoexpandthescopeofWebpack'sfileconcerns.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/7185/.

Page 531: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyInstallthepluginandaddittodevDependenciesofpackage.jsonwiththefollowing:

npminstallhtml-webpack-plugin--save-dev

Page 532: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...First,you'llneedtoincorporatethepluginintopackage.jsonifitisn'talready:

[package.json]

{

"name":"mva-bundling",

"scripts":{

"start":"tsc&&webpack&&concurrently'npmruntsc:w'

'npmrunwp:w''npmrunlite'",

"lite":"lite-server",

"postinstall":"npminstall-S@types/node@types/core-js",

"tsc":"tsc",

"tsc:w":"tsc-w",

"wp":"webpack",

"wp:w":"webpack--watch"

},

"dependencies":{

"@angular/common":"2.0.0",

"@angular/compiler":"2.0.0",

"@angular/core":"2.0.0",

"@angular/platform-browser":"2.0.0",

"@angular/platform-browser-dynamic":"2.0.0",

"core-js":"^2.4.1",

"reflect-metadata":"^0.1.3",

"rxjs":"5.0.0-beta.12",

"zone.js":"^0.6.23"

},

"devDependencies":{

"concurrently":"^2.2.0",

"html-webpack-plugin":"^2.22.0",

"imports-loader":"^0.6.5",

"lite-server":"^2.2.2",

"typescript":"^2.0.2",

"webpack":"^1.13.2"

}

}

Oncethismoduleisinstalled,defineitsoperationinsidetheWebpackconfig:

[webpack.config.js]

varHtmlWebpackPlugin=require('html-webpack-plugin');

module.exports={

entry:"./src/main.js",

output:{

path:"./dist",

filename:"bundle.js"

},

plugins:[newHtmlWebpackPlugin({

Page 533: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

template:'./src/index.html'

})]

};

ThisspecifiestheoutputHTMLfilethatwillservetheentireapplication.SincethepluginwillautomaticallygeneratetheHTMLfileforyou,you'llneedtomodifytheexistingonethatisdesignatedasthetemplate:

[src/index.html]

<html>

<head>

<title>Angular2MinimumViableApplication</title>

</head>

<body>

<app-root></app-root>

</body>

</html>

Finally,becauseindex.htmlisnowservedoutofthedist/directory,you'llneedtoconfigurethedevelopmentservertoservefilesoutofthere.Sincelite-serverisjustawrapperforBrowserSync,youcanspecifybaseDirinsideabs-config.jsonfile,whichyoushouldcreatenow:

[bs-config.json]

{

"server":{"baseDir":"./dist"}

}

Page 534: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Webpackisverymuchawareofthebundlethatitiscreating,andsoitmakessensethatyouwouldbeabletomaintainareferencetothisbundle(orbundles)anddirectlypipethosepathsintoanindex.htmlfile.ThepluginwillappendthescriptsattheendofthebodytoensuretheentireinitialDOMispresent.

Page 535: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoMigratingtheminimumviableAngular2applicationtoWebpackbundlingdescribeshowtointegrateWebpackintoyourAngularapplicationbuildprocessIncorporatingshimsandpolyfillsintoWebpackgivesyouahandywayofmanagingAngular2polyfilldependencies

Page 536: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SettingupanapplicationwithAngularCLIIntandemwiththeAngular2framework,theAngularteamalsosupportsabuildtoolthatcancreate,build,andrunanAngular2applicationrightoutofthebox.What'smore,itincludesageneratorthatcancreatestyle-guide-compliantfilesanddirectoriesforvariousapplicationpiecesfromthecommandline.

Note

Thecode,links,andaliveexampleofthisareavailableathttp://ngcookbook.herokuapp.com/4068/.

Page 537: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyAngular'sCLIisannpmmodule.You'llneedtohaveNode.jsinstalledonyoursystem—v7.0.0orlaterworksasasuitablerecentreleasethat'scompatiblewiththeAngularCLI.

Tip

Thereisanotheroptionyouhave:manageyourNodeenvironmentswithnvm,theNodeversionmanager.ThisgivesyouatransparentwrapperthatcanseparatelymanageenvironmentswiththeNodeversionaswellastheinstallednpmpackagesinthatenvironment.Ifyou'veeverdealtwithmessinessinvolvingsudonpminstall-g,youwillbedelightedbythistool.

OnceNodeisinstalled(andifyouusenvm,you'veselectedwhichenvironmenttouse),installtheAngularCLI:

npminstall-gangular-cli

Page 538: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...AngularCLIcomesreadytogenerateafullyworkingAngular2application.TocreateanapplicationnamedPublisherApp,invokethefollowingcommand:

ngnewpublisher

TheAngularCLIwilldutifullyassembleallthefilesneededforaminimalAngular2TypeScriptapplication,initializeaGitrepository,andinstallalltherequirednpmdependencies.Thecreatedfilelistshouldlookasfollows:

createREADME.md

createsrc/app/app.component.css

createsrc/app/app.component.html

createsrc/app/app.component.spec.ts

createsrc/app/app.component.ts

createsrc/app/app.module.ts

createsrc/app/index.ts

createsrc/app/shared/index.ts

createsrc/environments/environment.prod.ts

createsrc/environments/environment.ts

createsrc/favicon.ico

createsrc/index.html

createsrc/main.ts

createsrc/polyfills.ts

createsrc/styles.css

createsrc/test.ts

createsrc/tsconfig.json

createsrc/typings.d.ts

createangular-cli.json

createe2e/app.e2e-spec.ts

createe2e/app.po.ts

createe2e/tsconfig.json

create.gitignore

createkarma.conf.js

createpackage.json

createprotractor.conf.js

createtslint.json

Usecdpublishertomoveintotheapplication'sdirectory,whichwillallowyoutoinvokealltheproject-specificAngularCLIcommands.

Runningtheapplicationlocally

Torunthisapplication,startuptheserver:

ngserve

Thedefaultapplicationpagewillbeavailableonlocalhost:4200.

Page 539: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Testingtheapplication

Toruntheapplication'sunittests,usethis:

ngtest

Toruntheapplication'send-to-endtests,usethis:

nge2e

Page 540: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Let'sroughlygothroughwhateachofthesefilesoffertoyou:

Projectconfigurationfilesangular-cli.jsonistheconfigurationfilespecifyinghowtheAngularCLIshouldbundleandmanageyourapplication'sfilesanddirectories.package.jsonisthenpmpackageconfigurationfile.Insideit,you'llfindscriptsandcommand-linetargetsthattheAngularCLIcommandswilltieinto.

TypeScriptconfigurationfilestslint.jsonspecifiestheconfigurationforthetslintnpmpackage.TheAngularCLIcreatesforyoualintcommandfor.tsfileswithnpmrunlint.src/tsconfig.jsonispartoftheTypeScriptspecification;itinformsthecompilerthatthisistherootoftheTypeScriptproject.Itscontentsdefinehowthecompilationshouldoccur,anditspresenceenablesthetsccommandtousethisdirectoryastherootcompilationdirectory.e2e/tsconfig.jsonistheend-to-endTypeScriptcompilerconfigurationfile.src/typings.d.tsisthespecificationfileforthetypingsnpmmodule.ItallowsyoutodescribehowexternalmodulesshouldbewrappedandincorporatedintotheTypeScriptcompiler.Thistypings.d.tsfilespecifiestheSystemnamespaceforSystemJS.

Testconfigurationfileskarma.conf.jsistheconfigurationfileforKarma,thetestrunnerfortheprojectprotractor.conf.jsistheconfigurationfileforProtractor,theend-to-endtestframeworkfortheprojectsrc/test.tsdescribestotheKarmaconfigurationhowtostartupthetestrunnerandwheretofindthetestfilesthroughouttheapplication

Coreapplicationfilessrc/index.htmlistherootapplicationfilethatisservedtoruntheentiresingle-pageapplication.CompiledJSandotherstaticassetswillbeautomaticallyaddedtothisfilebythebuildscript.src/main.tsisthetop-levelTypeScriptfilethatservestobootstrapyourapplicationwithitsAppModuledefinition.src/polyfills.tsisjustafilethatkeepsthelonglistofimportedpolyfillmodulesoutofmain.ts.src/styles.cssistheglobalapplicationstylefile.

Environmentfilessrc/environments/environment.tsisthedefaultenvironmentconfigurationfile.Specifyingdifferentenvironmentswhenbuildingandtestingyourapplicationwilloverride

Page 541: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

these.src/environments/environment.prod.tsistheprodenvironmentconfiguration,whichcanbeselectedfromthecommandlinewith--prod.

AppComponentfiles

EveryAngular2applicationhasatop-levelcomponent,andAngularCLIcallsthisAppComponent.

src/app/app.component.tsisthecoreTypeScriptcomponentclassdefinition.Thisiswhereallofthelogicthatcontrolsthiscomponentshouldgo.src/app/app.component.htmlandsrc/app/app.component.cssarethetemplatingandstylingfilesspecifictoAppComponent.RecallthatstylingspecifiedinComponentMetadataisencapsulatedonlytothiscomponent.src/app/app.module.tsistheNgModuledefinitionforAppComponent.src/app/index.tsisthefilethatinformstheTypeScriptcompilerwhichmodulesareavailableinsidethisdirectory.Anymodulesthatareexportedinthisdirectoryandusedelsewhereintheapplicationmustbespecifiedhere.

AppComponenttestfilessrc/app/app.component.spec.tsaretheunittestsforAppComponente2e/app.e2e-spec.tsaretheend-to-endtestsforAppComponente2e/app.po.tsisthepageobjectdefinitionforuseinAppComponentend-to-endtesting

Page 542: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Whenlookingattheentireprojectcodebase,thebulkofthefilesbreakdownintofourcategories:

Filesthataresenttothebrowser:Thisincludesyouruncompiled/unminifiedapplicationfilesandalsothecompiled/minifiedfiles.Whendevelopingyourapplication,youwanttobeabletotestyourapplicationlocallywithuncompiledfiles.Youalsowanttobeabletoshipyourapplicationtoproductionwithcompiledandminifiedfiles,whichoptimizesbrowserperformance.Theuncompiled/unminifiedfilesarecollectedbythebuildscripts,tobecombinedintothecompiled/minifiedfiles.Filesusedfortesting:Thetestfilesthemselvesareusuallysprinkledthroughoutyourapplicationandarenotcompiled.Thiscategoryalsoincludesconfigurationfilesandtestscriptsthatcontrolwhatactuallyhappenswhenyourunthetestsandwherethetestrunnerscanfindthetestfilesinyourprojectdirectory.Filesthatcontrolyourdevelopmentenvironment:Dependingonyoursetup,yoursingle-pageapplicationmayrunbyitself(withnobackendcodebase),oritmaybebuiltalongsideasubstantialbackendcodebasethatexposesAPIsandotherserver-sidebehavior.Quickstartrepositoriesorapplicationgenerators(suchasAngularCLI)usuallyprovideyouwithaminimalHTTPservertogetyouoffthegroundandserveyourstaticassetstothebrowser.Howexactlyyourunyourdevelopmentenvironmentwillvary,butthefilesinthiscategorymanagehowyourapplicationwillworkbothlocallyandinproduction.Filesthatcompileyourapplication:Thefilesyoueditinyourcodeeditorofchoicearenottheonesthatreachthebrowserinaproductionapplication.Buildscriptsareusuallysetuptocombineallyourfilesintothesmallestandfewestfilespossible.Frequently,thiswillmeanasinglecompiledJSandcompiledCSSfiledeliveredtothebrowser.Thesefileswillminifyyourcodebase,compileTypeScriptintovanillaJavaScript,selectenvironmentfilesandothercontext-specificfiles,andorganizefileincludesandotherfilesandmoduledependenciessothatyourapplicationworkswhenit'scompiled.Usually,thefilestheycreatewillbedumpedintoadistdirectory,whichwillcontainfilesthatareservedtothebrowserinproduction.

Page 543: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoComposingpackage.jsonforaminimumviableAngular2applicationdescribeshowallthepiecesworkforthecorenodeprojectfileConfiguringTypeScriptforaminimumviableAngular2applicationtalksabouthowtoconfigurecompilationtosupportanAngular2projectPerformingin-browsertranspilationwithSystemJSdemonstrateshowSystemJScanbeusedtoconnectuncompiledstaticfilestogetherComposingapplicationfilesforaminimumviableAngular2applicationwalksyouthroughhowtocreateanextremelysimpleAngular2appfromscratchIncorporatingshimsandpolyfillsintoWebpackgivesyouahandywayofmanagingAngular2polyfilldependencies

Page 544: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter9.Angular2TestingThischapterwillcoverthefollowingrecipes:

CreatingaminimumviableunittestsuitewithKarma,Jasmine,andTypeScriptWritingaminimumviableunittestsuiteforasimplecomponentWritingaminimumviableend-to-endtestsuiteforasimpleapplicationUnittestingasynchronousserviceUnittestingacomponentwithaservicedependencyusingstubsUnittestingacomponentwithaservicedependencyusingspies

Page 545: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionWritingtestsislikebrushingyourteeth.Youcangetawaywithskippingitforawhile,butit'llcatchupwithyoueventually.

Theworldoftestingisawashwithconflictingideologies,platitudes,andgrandstanding.What'smore,thereisadizzyingarrayoftoolsavailablethatallowyoutowriteandrunyourtestsindifferentways,automateyourtests,oranalyzeyourtestcoverageorcorrectness.Ontopofthat,eachdeveloper'sutilityandstyleoftestingisunique;someonehackingawayatapre-seedstartupwillnothavethesamerequirementsasadeveloperthatispartofalargeteaminsideaFortune500company.

ThegoalofthischapteristowalkyouthroughtheavailabletestingutilitiesthattheAngular2frameworkcomeswithoutofthebox,aswellassomestrategiesfordeployingtheseutilities.TherecipeswillfocusonunittestsratherthanE2Etests,asanoverwhelmingmajorityofrobusttestsuiteswillbeunittests.

Page 546: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

CreatingaminimumviableunittestsuitewithKarma,Jasmine,andTypeScriptBeforeyoujumpintotheintricaciesoftestinganAngular2application,it'simportanttofirstexaminethesupportinginfrastructurethatwillmakerunningthesetestspossible.ThebulkofofficialAngularresourcesoffertestsontopofKarmaandJasmine,andthere'snoreasontorocktheboatonthisone,asthesearebothfinetestingtools.Thatsaid,it'sawholenewworldwithTypeScriptinvolved,andusingthemintestswillrequiresomeconsiderations.

Thisrecipewilldemonstratehowtoputtogetheraverysimpleunittestsuite.ItwilluseKarmaandJasmineasthetestinfrastructure,TypeScriptandWebpackforcompilationandmodulesupport,andPhantomJSasthetestbrowser.Forthoseunfamiliarwiththesetools,here'sabitaboutthem:

Karmaisaunittestrunner.YourunteststhroughKarmaonthecommandline.Ithastheabilitytostartupatestserverthatunderstandshowtofindtestfilesandservethemtothetestbrowser.Jasmineisatestframework.Whenyouusekeywordssuchas"it"and"describe,"rememberthattheyarepartofJasmineunittests.ItintegrateswithKarmaandunderstandshowtoexposeandrunthetestsyou'vewritten.PhantomJSisaheadlesswebkitbrowser.(HeadlessmeansitrunsasaprocessthatdoesnothaveavisibleuserinterfacebutstillconstructsaDOMandhasaJSruntime.)Unittestsrequireabrowsertorun,astheJavaScriptunittestsaredesignedtoexecuteinsideabrowserruntime.Karmasupportsalargenumberofbrowserpluginstorunthetestson,includingstandardbrowserssuchasChromeandFirefox.Ifyouweretoincorporatethesebrowserplugins,Karmawouldstartupaninstanceofthebrowserandrunthetestsinsideit.Forthepurposeofcreatingaminimumviableunittestsuite,youarefinedoingthetestinginsideaheadlessbrowser,whichwillcleanlyreportitsresultstothecommandline.Ifyouwanttorunyourtestsinsideanactualbrowser,Karmawillexposetheserverataspecifiedport,whichyoucanaccessdirectly,forexample,visitinghttp://localhost:9876inthedesiredtestbrowser.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/3998/.

Page 547: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyStartoutwithapackage.jsonfile:

[package.json]

{}

Note

ThisstillneedstobeavalidJSONfile,asnpmneedstobeabletoparseitandaddtoit.

Page 548: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Startoffbycreatingthefilethatwillbetested.YouintendtouseTypeScript,sogoaheadanduseitssyntaxhere:

[src/article.ts]

exportclassArticle{

title:string=

"LabMiceStrikeforImprovedWorkingConditions,Benefits"

}

Writingaunittest

WiththeArticleclassdefined,youcannowimportitintoanewtestfile,article.spec.ts,anduseit.

Note

Jasminetestfiles,byconvention,aresuffixedwith.spec.ts.TestfilesgeneratedbytheAngularCLIwillexistalongsidethefiletheytest,butbynomeansisthismandatory.YoucandefineyourconventioninsideyourKarmaconfigurationlateron.

StartoffbyimportingtheArticleclassandcreateanemptyJasminetestsuiteusingdescribe:

[src/article.spec.ts]

import{Article}from'./article';

describe('Articleunittests',()=>{

});

Note

describedefinesaspecsuite,whichincludesastringtitlecalledArticleunittests,andananonymousfunction,whichcontainsthesuite.Aspecsuitecanbenestedinsideanotherspecsuite.

Insideadescribesuitefunction,youcandefinebeforeEachandafterEach,whicharefunctionsthatexecutebeforeandaftereachunittestisdefinedinsidethesuite.Therefore,itispossibletodefinenestedsetuplogicforunittestsusingnesteddescribeblocks.

Insidethespecsuitefunction,writetheunittestthatisusingit:

[src/article.spec.ts]

import{Article}from'./article';

Page 549: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

describe('Articleunittests',()=>{

it('Hascorrecttitle',()=>{

leta=newArticle();

expect(a.title)

.toBe("LabMiceStrikeforImprovedWorkingConditions,

Benefits");

});

});

NotethatboththecodeandthetestarewritteninTypeScript.

ConfiguringKarmaandJasmine

First,installKarma,theKarmaCLI,Jasmine,andtheKarmaJasmineplugin:

npminstallkarmajasmine-corekarma-jasmine--save-dev

npminstallkarma-cli-g

Alternately,ifyouwanttosaveafewkeystrokes,thefollowingisequivalent:

npmi-Dkarmajasmine-corekarma-jasmine

npmikarma-cli-g

Karmareadsitsconfigurationoutofakarma.conf.jsfile,socreatethatnow:

[karma.conf.js]

module.exports=function(config){

config.set({

})

}

KarmaneedstoknowhowtofindthetestfilesandalsohowtouseJasmine:

[karma.conf.js]

module.exports=function(config){

config.set({

frameworks:[

'jasmine'

],

files:[

'src/*.spec.js'

],

plugins:[

'karma-jasmine',

]

})

}

ConfiguringPhantomJS

Page 550: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

PhantomJSallowsyoutodirecttestsentirelyfromthecommandline,butKarmaneedstounderstandhowtousePhantomJS.InstallthePhantomJSplugin:

npminstallkarma-phantomjs-launcher--save-dev

Next,incorporatethispluginintotheKarmaconfig:

[karma.conf.js]

module.exports=function(config){

config.set({

browsers:[

'PhantomJS'

],

frameworks:[

'jasmine'

],

files:[

'src/*.spec.js'

],

plugins:[

'karma-jasmine',

'karma-phantomjs-launcher'

]

})

}

KarmanowknowsithastorunthetestsinPhantomJS.

CompilingfilesandtestswithTypeScript

Ifyou'repayingattentionclosely,you'llnotethattheKarmaconfigisreferencingtestfilesthatdonotexist.Sinceyou'reusingTypeScript,youmustcreatethesefiles.InstallTypeScriptandtheJasminetypedefinitions:

npminstalltypescript@types/jasmine--save-dev

Addscriptdefinitionstoyourpackage.json:

[package.json]

{

"scripts":{

"tsc":"tsc",

"tsc:w":"tsc-w"

},

"devDependencies":{

"@types/jasmine":"^2.5.35",

"jasmine-core":"^2.5.2",

"karma":"^1.3.0",

"karma-cli":"^1.0.1",

"karma-jasmine":"^1.0.2",

Page 551: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

"karma-phantomjs-launcher":"^1.0.2",

"typescript":"^2.0.3"

}

}

Createatsconfig.jsonfile.Sinceyou'refinewiththecompiledfilesresidinginthesamedirectory,asimpleonewilldo:

[tsconfig.json]

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"moduleResolution":"node"

}

}

Tip

Youwouldprobablynotdoitthiswayforaproductionapplication,butforaminimumviablesetup,thiswilldoinapinch.Aproductionapplicationwouldmostlikelyputcompiledfilesintoanentirelydifferentdirectory,frequentlynameddist/.

IncorporatingWebpackintoKarma

Ofcourse,you'llneedawayofresolvingmoduledefinitionsforcodeandtests.Karmaisn'tcapableofdoingthisonitsown,soyou'llneedsomethingtodothis.Webpackisperfectlysuitableforsuchatask,andKarmahasaterrificpluginthatallowsyoutopreprocessyourtestfilesbeforetheyreachthebrowser.

InstallWebpackanditsKarmaplugin:

npminstallwebpackkarma-webpack--save-dev

ModifytheKarmaconfigtospecifyWebpackasthepreprocessor.Thisallowsyourmoduledefinitionstoberesolvedproperly:

[karma.conf.js]

module.exports=function(config){

config.set({

browsers:[

'PhantomJS'

],

frameworks:[

'jasmine'

],

files:[

'src/*.spec.js'

Page 552: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

],

plugins:[

'karma-webpack',

'karma-jasmine',

'karma-phantomjs-launcher'

],

preprocessors:{

'src/*.spec.js':['webpack']

}

})

}

Writingthetestscript

YoucankickofftheKarmaserverwiththefollowing:

karmastartkarma.conf.js

Thiswillinitializethetestserverandrunthetests,watchingforchangesandrerunningthetests.However,thissidestepsthefactthattheTypeScriptfilesrequirecompilationinthefilesthatKarmaiswatching.TheTypeScriptcompileralsohasafilewatcherthatwillrecompileonthefly.Youwouldlikebothofthesetorecompilewheneveryousavechangestoasourcecodefile,soitmakessensetorunthemsimultaneously.Theconcurrentlypackageissuitableforthistask.

Note

concurrentlynotonlyallowsyoutorunmultiplecommandsatonce,butalsotokillthemallatonce.Withoutit,akillsignalfromthecommandlinewouldonlytargetwhicheverprocesswasrunmostrecently,ignoringtheprocessthatisrunninginthebackground.

Installconcurrentlywiththefollowing:

npminstallconcurrently--save-dev

Finally,buildyourtestscripttorunKarmaandtheTypeScriptcompilersimultaneously:

[package.json]

{

"scripts":{

"test":"concurrently'npmruntsc:w''karmastart

karma.conf.js'",

"tsc":"tsc",

"tsc:w":"tsc-w"

},

"devDependencies":{

"@types/jasmine":"^2.5.35",

"concurrently":"^3.1.0",

"jasmine-core":"^2.5.2",

"karma":"^1.3.0",

"karma-cli":"^1.0.1",

Page 553: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

"karma-jasmine":"^1.0.2",

"karma-phantomjs-launcher":"^1.0.2",

"karma-webpack":"^1.8.0",

"typescript":"^2.0.3",

"webpack":"^1.13.2"

}

}

Withthis,youshouldbeabletorunyourtests:

npmtest

Ifeverythingisdonecorrectly,theKarmaservershouldbootupandrunthetests,outputtingthefollowing:

PhantomJS2.1.1(Linux0.0.0):Executed1of1SUCCESS(0.038secs/0.001

secs)

Page 554: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...KarmaandJasmineworktogethertodelivertestfilestothetestbrowser.TypeScriptandWebpackaretaskedwithconvertingyourTypeScriptfilesintoaJavaScriptformatthatwillbeusablebythetestbrowser.

Page 555: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...AninterestingconsiderationofthissetupishowexactlyTypeScriptishandled.

BoththecodeandtestfilesarewritteninTypeScript,whichallowsyoutousetheES6modulenotation,asopposedtosomemix-and-matchstrategy.However,thisleavesyouwithsomechoicestomakeonhowthetestsetupshouldwork.

Thetestsneedtobeabletousedifferentpiecesofyourapplicationinapiecemealfashion,asopposedtothestandardapplicationsetupwhereallthemodulesgetpulledtogetheratonce.ThisrecipehadTypeScriptindependentlycompilethe.tsfiles,anditthendirectedKarmatowatchtheresultant.jsfiles.Thisisperhapseasiertocomprehendbysomeonewhoiseasingintotests,butitmightnotbethemostefficientwaytogoaboutit.KarmaalsosupportsTypeScriptplugins,whichallowyoutopreprocessthefilesintoTypeScriptbeforehandingthemofftotheWebpackpreprocessor.

Karmasupportsthechainingofpreprocesssteps,whichwillbeusefulifyouwanttocompiletheTypeScriptontheflyaspartofpreprocessing.

Page 556: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoWritingaminimumviableunittestsuiteforasimplecomponentshowsyouabasicexampleofunittestingAngular2componentsUnittestingasynchronousservicedemonstrateshowaninjectionismockedinunittestsUnittestingacomponentwithaservicedependencyusingStubsshowshowyoucancreateaservicemocktowriteunittestsandavoiddirectdependenciesUnittestingacomponentwithaservicedependencyusingSpiesshowshowyoucankeeptrackofservicemethodinvocationsinsideaunittest

Page 557: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WritingaminimumviableunittestsuiteforasimplecomponentUnittestsarethebreadandbutterofyourapplicationtestingprocess.Theyexistasacompaniontoyoursourcecode,andmostofthetime,thebulkofyourapplicationtestswillbeunittests.Theyarelightweight,runquickly,areeasytoreadandreasonabout,andcangivecontextastohowthecodeshouldbeusedandhowitmightbehave.

SettingupKarma,Jasmine,TypeScript,andAngular2alongwithalltheconnectingconfigurationsbetweenthemisabitofanimposingtask;itwasdeemedtobeoutofthescopeofthischapter.It'snotaveryinterestingdiscussiontogetallofthemtoworktogether,especiallysincetherearealreadysomanyexampleprojectsthathaveputtogethertheirownsetupsforyou.It'sfarmoreinterestingtodivedirectlyintotheteststhemselvesandseehowtheycanactuallyinteractwithAngular2.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/3935/.

Page 558: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyThisrecipewillassumeyouareusingaworkingAngular2testingenvironment.TheoneprovidedintheapplicationgeneratedbytheAngularCLIisideal.Testscanberuninthisenvironmentwiththefollowingcommandinsidetheprojectdirectory:

ngtest

Beginwiththefollowingcomponent:

[src/app/article/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-article',

template:`

<h1>

{{title}}

</h1>

`

})

exportclassArticleComponent{

title:string='CaptainHookSuesOverSporkSnafu';

}

Yourgoalistoentirelyfleshoutarticle.component.spec.tstotestthisclass.

Page 559: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...ThesimplestpossibletestyoucanthinkofistheonethatwillsimplycheckthatyouareabletoinstantiateaninstanceofArticleComponent.Beginwiththattest:

[src/app/article/article.component.spec.ts]

import{ArticleComponent}from'./article.component';

describe('Component:Article',()=>{

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

});

Nothingtrickyisgoingonhere.SinceArticleComponentisjustaplainoldTypeScriptclass,nothingispreventingyoufromcreatinganinstanceandinspectingitinthememory.

However,forittoactuallybehavelikeanAngular2component,you'llneedsomeothertools.

UsingTestBedandasync

WhenyoutrytopuppetanAngular2environmentforthecomponentinatest,thereareanumberofconsiderationsyou'llneedtoaccountfor.First,Angular2unittestsheavilyrelyuponTestBed,whichcanbethoughtofasyourtestingmultitool.

ThedenominationofunittestswhendealingwithacomponentinvolvesComponentFixture.TestBed.createComponent()willcreateafixturewrappinganinstanceofthedesiredcomponent.

Tip

Theneedforfixturesiscenteredinhowunittestsaresupposedtowork.AnArticleComponentdoesnotmakesensewheninstantiatedasitwaswiththeinitialtestyouwrote.ThereisnoDOMelementtoattachto,norunningapplication,andsoon.Itdoesn'tmakesenseforthecomponentunitteststohaveanexplicitdependencyonthesethings.So,ComponentFixtureisAngular'swayoflettingyoutestonlytheconcernsofthecomponentasitwouldnormallyexist,withoutworryingaboutallthemessinessofitsinnatedependencies.

TheTestBedfixture'sasynchronousbehaviormandatesthatthetestlogicisexecutedinsideanasync()wrapper.

Tip

Theasync()wrappersimplyrunsthetestinsideitsownzone.Thisallowsthetestrunnertowait

Page 560: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

foralltheasynchronouscallsinsidethetesttocompletethembeforeendingthetest.

BeginbyimportingTestBedandasyncfromtheAngulartestingmoduleandputtogethertheskeletonfortwomoreunittests:

[src/app/article/article.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{ArticleComponent}from'./article.component';

describe('Component:Article',()=>{

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

it('shouldhavecorrecttitle',async(()=>{

}));

it('shouldrendertitleinanh1tag',async(()=>{

}));

});

Nowthatyouhavetheskeletonsforthetwotestsyou'dliketowrite,it'stimetouseTestBedtodefinethetestmodule.Angular2componentsarepairedwithamoduledefinition,butwhenperformingunittests,you'llneedtousetheTestBedmodule'sdefinitionforthecomponenttoworkproperly.ThiscanbedonewithTestBed.configureTestModule(),andyou'llwanttoinvokethisbeforeeachtest.

Jasmine'sdescribeallowsyoutogroupbeforeEachandafterEachinsideit,anditisperfectforusehere:

[src/app/article/article.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{ArticleComponent}from'./article.component';

describe('Component:Article',()=>{

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

describe('Async',()=>{

beforeEach(()=>{

TestBed.configureTestingModule({

declarations:[

ArticleComponent

],

});

});

Page 561: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

it('shouldhavecorrecttitle',async(()=>{

}));

it('shouldrendertitleinanh1tag',async(()=>{

}));

});

});

CreatingaComponentFixture

TestBedgivesyoutheabilitytocreateafixture,butyouhaveyettoactuallydoit.You'llneedafixtureforboththeasynctests,soitmakessensetodothisinbeforeEachtoo:

[src/app/article/article.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{ArticleComponent}from'./article.component';

describe('Component:Article',()=>{

letfixture;

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

describe('Async',()=>{

beforeEach(()=>{

TestBed.configureTestingModule({

declarations:[

ArticleComponent

],

});

fixture=TestBed.createComponent(ArticleComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldhavecorrecttitle',async(()=>{

}));

it('shouldrendertitleinanh1tag',async(()=>{

}));

});

});

Tip

Here,fixtureisassignedtoundefinedintheafterEachteardown.Thisistechnically

Page 562: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

superfluousforthepurposeofthesetests,butitisgoodtogetintothehabitofperformingarobustteardownofsharedvariablesinunittests.Thisisbecauseoneofthemostfrustratingthingstodebuginatestsuiteistestvariablebleed.Afterall,thesearejustfunctionsrunninginasequenceinabrowser.

Nowthatthefixtureisdefinedforeachtest,youcanuseitsmethodstoinspecttheinstantiatedcomponentindifferentways.

Forthefirsttest,you'dliketoinspecttheArticleComponentobjectitselffromwithinComponentFixture.ThisisexposedwiththecomponentInstanceproperty:

[src/app/article/article.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{ArticleComponent}from'./article.component';

describe('Component:Article',()=>{

letexpectedTitle='CaptainHookSuesOverSporkSnafu';

letfixture;

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

describe('Async',()=>{

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

ArticleComponent

],

});

fixture=TestBed.createComponent(ArticleComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldhavecorrecttitle',async(()=>{

expect(fixture.componentInstance.title)

.toEqual(expectedTitle);

}));

it('shouldrendertitleinanh1tag',async(()=>{

}));

});

});

Forthesecondtest,youwantaccesstotheDOMthatthefixturehasattachedthecomponentinstanceto.TherootelementthatthecomponentistargetingisexposedwiththenativeElement

Page 563: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

property:

[src/app/article/article.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{ArticleComponent}from'./article.component';

describe('Component:Article',()=>{

letexpectedTitle='CaptainHookSuesOverSporkSnafu';

letfixture;

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

describe('Async',()=>{

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

ArticleComponent

],

});

fixture=TestBed.createComponent(ArticleComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldhavecorrecttitle',async(()=>{

expect(fixture.componentInstance.title)

.toEqual(expectedTitle);

}));

it('shouldrendertitleinanh1tag',async(()=>{

expect(fixture.nativeElement.querySelector('h1')

.textContent).toContain(expectedTitle);

}));

});

});

Ifyourunthesetests,youwillnoticethatthelasttestwillfail.Thetestseesanemptystringinside<h1></h1>.Thisisbecauseyouarebindingavalueinthetemplatetoacomponentmember.Sincethefixturecontrolstheentireenvironmentsurroundingthecomponent,italsocontrolsthechangedetectionstrategy—which,here,istonotrununtilitistoldtodoso.YoucantriggeraroundofchangedetectionusingthedetectChanges()method:

[src/app/article/article.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{ArticleComponent}from'./article.component';

Page 564: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

describe('Component:Article',()=>{

letexpectedTitle='CaptainHookSuesOverSporkSnafu';

letfixture;

it('shouldcreateaninstance',()=>{

letcomponent=newArticleComponent();

expect(component).toBeTruthy();

});

describe('Async',()=>{

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

ArticleComponent

],

});

fixture=TestBed.createComponent(ArticleComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldhavecorrecttitle',async(()=>{

expect(fixture.componentInstance.title)

.toEqual(expectedTitle);

}));

it('shouldrendertitleinanh1tag',async(()=>{

fixture.detectChanges();

expect(fixture.nativeElement.querySelector('h1')

.textContent).toContain(expectedTitle);

}));

});

});

Withthis,youshouldseeKarmarunandpassallthreetests.

Page 565: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Whenitcomestotestingcomponents,fixtureisyourfriend.Itgivesyoutheabilitytoinspectandmanipulatethecomponentinanenvironmentthatitwillbehavecomfortablyin.Youarethenabletomanipulatetheinstancesofinputmadetothecomponent,aswellasinspecttheiroutputandresultantbehavior.

Thisisthecoreofunittesting:the"thing"youaretesting—here,acomponentclass—shouldbetreatedasablackbox.Youcontrolwhatgoesintothebox,andyourtestsshouldmeasureanddefinewhattheyexpecttocomeoutofthebox.Ifthetestsaccountforallthepossiblecasesofinputandoutput,thenyouhaveachieved100percentunittestcoverageofthatthing.

Page 566: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoCreatingaminimumviableunittestsuitewithKarma,Jasmine,andTypeScriptgivesyouagentleintroductiontounittestswithTypeScriptUnittestingasynchronousservicedemonstrateshowaninjectionismockedinunittestsUnittestingacomponentwithaservicedependencyusingStubsshowshowyoucancreateaservicemocktowriteunittestsandavoiddirectdependenciesUnittestingacomponentwithaservicedependencyusingSpiesshowshowyoucankeeptrackofservicemethodinvocationsinsideaunittest

Page 567: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Writingaminimumviableend-to-endtestsuiteforasimpleapplicationEnd-to-endtesting(ore2eforshort)isontheotherendofthespectrumasfarasunittestingisconcerned.Theentireapplicationexistsasablackbox,andtheonlycontrolsatyourdisposal—forthesetests—areactionstheusermighttakeinsidethebrowser,suchasfiringclickeventsornavigatingtoapage.Similarly,thecorrectnessoftestsisonlyverifiedbyinspectingthestateofthebrowserandtheDOMitself.

Moreexplicitly,anend-to-endtestwill(insomeform)startupanactualinstanceofyourapplication(orasubsetofit),navigatetoitinanactualbrowser,dostufftoapage,andlooktoseewhathappensonthepage.It'sprettymuchascloseasyouaregoingtogettohavinganactualpersonsitdownanduseyourapplication.

Inthisrecipe,you'llputtogetheraverybasicend-to-endtestsuitesothatyoumightbetterunderstandtheconceptsinvolved.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/8985/.

Page 568: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyYou'llbeginwiththecodefilescreatedintheminimumviableapplicationrecipefromChapter8,ApplicationOrganizationandManagement.Themostimportantfilesthatyou'llbeeditinghereareAppComponentandpackage.json:

[package.json]

{

"scripts":{

"start":"tsc&&concurrently'npmruntsc:w''npmrunlite'",

"lite":"lite-server",

"postinstall":"npminstall-s@types/node@types/core-js",

"tsc":"tsc",

"tsc:w":"tsc-w"

},

"dependencies":{

"@angular/common":"2.1.0",

"@angular/compiler":"2.1.0",

"@angular/core":"2.1.0",

"@angular/platform-browser":"2.1.0",

"@angular/platform-browser-dynamic":"2.1.0",

"core-js":"^2.4.1",

"reflect-metadata":"^0.1.3",

"rxjs":"5.0.0-beta.12",

"systemjs":"0.19.27",

"zone.js":"^0.6.23"

},

"devDependencies":{

"concurrently":"^2.2.0",

"lite-server":"^2.2.2",

"typescript":"^2.0.2"

}

}

[app/app.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-root',

template:'<h1>AppComponenttemplate!</h1>'

})

exportclassAppComponent{}

Page 569: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TheAngularteammaintainstheProtractorproject,whichbymanyaccountsisthebestwaytogoaboutperformingend-to-endtestsonyourapplications,atleastinitially.Itcomeswithalargenumberofutilitiesoutoftheboxtomanipulatethebrowserwhenwritingyourtests,andexplicitintegrationswithAngular2,soit'saterrificplacetostart.

GettingProtractorupandrunning

ProtractorreliesonSeleniumtoautomatethebrowser.ThespecificsofSeleniumaren'tespeciallyimportantforthepurposeofcreatingaminimumviablee2etestsuite,butyouwillneedtoinstallaJavaruntime:

sudoapt-getinstallopenjdk-8-jre

Tip

IrunUbuntu,sotheOpenJDKJavaRuntimeEnvironmentV8issuitableformypurposes.Yourdevelopmentsetupmaydiffer.RuntimesfordifferentoperatingsystemscanbefoundonOracle'swebsite.

Protractoritselfcanbeinstalledfromnpm,butitshouldbeglobal.You'llbeusingitwithJasmine,soinstallitanditsTypeScripttypingsaswell:

npminstalljasmine-core@types/jasmine--save-dev

npminstallprotractor-g

Tip

Youmayneedtofiddlewiththisconfiguration.Sometimes,itmayworkifyouinstallprotractorlocallyratherthanglobally.Errorsinvolvingwebdriver-managerarepartoftheprotractorpackage,sotheywillmostlikelybeinvolvedwhereyourprotractorpackageinstallationisaswell.

Itshouldcomeasnosurprisethatprotractorisconfiguredwithafile,socreateitnow:

[protractor.conf.js]

exports.config={

specs:[

'./e2e/**/*.e2e-spec.ts'

],

capabilities:{

'browserName':'chrome'

},

baseUrl:'http://localhost:3000/',

framework:'jasmine',

}

Page 570: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Noneofthesesettingsshouldsurpriseyou:

Thee2etestfilesaregoingtoliveinane2e/directoryandwillbesuffixedwith.e2e-spec.ts

ProtractorisgoingtospinupaChromeinstancethatitwillpuppeteerwithSeleniumTheserveryou'regoingtospinupwillexistatlocalhost:3000,andalltheURLsinsidetheProtractortestswillberelativetothisTheProtractortestswillbewrittenwiththeJasminesyntax

Forsimplicity,theserveryouarestartingupfortheend-to-endtestswillbethesamelite-serveryou'vebeenusingallalong.Whenitstartsup,lite-serverwillopenupabrowserwindowofitsown,whichwillprovetobeabitannoyinghere.SinceitisathinwrapperforBrowserSync,youcanconfigureittonotdothisbysimplydirectingitnottodosoinaconfigfilethatisonlyusedwhenrunninge2etests.

Createthisfilenowinsidethetestdirectory:

[e2e/bs-config.json]

{

"open":false

}

Note

Thelite-serverwrapperwon'tfindthisautomatically,butyou'lldirectittothefileinamoment.

MakingProtractorcompatiblewithJasmineandTypeScript

First,createatsconfig.jsonfileinsidethetestdirectory:

[e2e/tsconfig.json]

{

"compilerOptions":{

"target":"es5",

"module":"commonjs",

"moduleResolution":"node"

}

}

Next,createtheactuale2etestfileskeleton:

[e2e/app.e2e-spec.ts]

describe('AppE2ETestSuite',()=>{

it('shouldhavethecorrecth1text',()=>{

});

});

Page 571: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ThisusesthestandardJasminesyntaxtodeclareaspecsuiteandanemptytestwithinit.

Beforefleshingoutthetest,youneedtoensurethatProtractorcanactuallyusethisfile.Installthets-nodepluginsothatProtractorcanperformthecompilationandusethesefilesine2etests:

npminstallts-node--save-dev

Next,instructProtractortousethistocompilethetestsourcefilesintoausableformat.Thiscanbedoneinitsconfigfile:

[protractor.conf.js]

exports.config={

specs:[

'./e2e/**/*.e2e-spec.ts'

],

capabilities:{

'browserName':'chrome'

},

baseUrl:'http://localhost:3000/',

framework:'jasmine',

beforeLaunch:function(){

require('ts-node').register({

project:'e2e'

});

}

}

Withallthis,you'releftwithaworkingbutemptyend-to-endtest.

Buildingapageobject

AnexcellentconventionthatIhighlyrecommendusingisthepageobject.

Note

Theideabehindthisisthatallofthelogicsurroundingtheinteractionwiththepagecanbeextractedintoitsownpageobjectclass,andtheactualtestbehaviorcanusethisabstractedpageobjectinsidetheclass.ThisallowstheteststobewrittenindependentlyoftheDOMstructureorroutingdefinitions,whichmakesforsuperiortestmaintenance.What'smore,itmakesyourteststotallyindependentofProtractor,whichmakesiteasiershouldyouwanttochangeyourend-to-endtestrunner.

Forthissimpleend-to-endtest,you'llwanttospecifyhowtoarriveatthispageandhowtoinspectittogetwhatyouwant.Definethepageobjectasfollowswithtwomembermethods:

[e2e/app.po.ts]

import{browser,element,by}from'protractor';

Page 572: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassAppPage{

navigate(){

browser.get('/');

}

getHeaderText(){

returnelement(by.css('app-rooth1')).getText();

}

}

navigate()instructsSeleniumtotherootpath(which,asyoumayrecall,isbasedonlocalhost:3000),andgetHeaderText()inspectsaDOMelementforitstextcontents.

Note

Notethatbrowser,element,andbyareallutilitiesimportedfromtheprotractormodule.Moreonthislaterintherecipe.

Writingthee2etest

Withalloftheinfrastructureinplace,youcannoweasilywriteyourend-to-endtest.You'llwanttoinstantiateanewpageobjectforeachtest:

[e2e/app.e2e-spec.ts]

import{AppPage}from'./app.po';

describe('AppE2ETestSuite',()=>{

letpage:AppPage;

beforeEach(()=>{

page=newAppPage();

});

it('shouldhavethecorrecth1text',()=>{

page.navigate();

expect(page.getHeaderText())

.toEqual('AppComponenttemplate!');

});

});

Scriptingthee2etests

Finally,you'llwanttogiveyourselftheabilitytoeasilyruntheend-to-endtestsuite.Seleniumisoftenbeingupdated,soitbehovesyoutoexplicitlyupdateitbeforeyourunthetests:

[package.json]

{

"scripts":{

Page 573: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

"pree2e":"webdriver-managerupdate&&tsc",

"e2e":"concurrently'npmrunlite---c=e2e/bs-config.json'

'protractorprotractor.conf.js'",

"start":"tsc&&concurrently'npmruntsc:w''npmrunlite'",

"lite":"lite-server",

"postinstall":"npminstall-s@types/node@types/core-js",

"tsc":"tsc",

"tsc:w":"tsc-w"

},

"dependencies":{

...

},

"devDependencies":{

...

}

}

Finally,Angular2needstointegratewithProtractorandbeabletotellitwhenthepageisreadytobeinteractedwith.ThisrequiresonemoreadditiontotheProtractorconfiguration:

[protractor.conf.js]

exports.config={

specs:[

'./e2e/**/*.e2e-spec.ts'

],

capabilities:{

'browserName':'chrome'

},

baseUrl:'http://localhost:3000/',

framework:'jasmine',

useAllAngular2AppRoots:true,

beforeLaunch:function(){

require('ts-node').register({

project:'e2e'

});

}

}

That'sall!Youshouldnowbeabletoruntheend-to-endtestsuitebyinvokingitwiththecorrespondingnpmscript:

npmrune2e

Thiswillstartupalite-serverinstance(withoutstartingupitsdefaultbrowser),andprotractorwillrunthetestsandexit.

Page 574: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Atthetopoftheapp.po.tspageobjectfile,youimportedthreetargetsfromProtractor:browser,element,andby.Here'sabitaboutthesetargets:

browserisaprotractorglobalobjectthatallowsyoutoperformbrowser-levelactions,suchasvisitingURLs,waitingforeventstooccur,andtakingscreenshots.elementisaglobalfunctionthattakesaLocatorandreturnsanElementFinder.ElementFinderisthepointofcontacttointeractwiththematchingDOMelement,ifitexists.byisaglobalobjectthatexposesseveralLocatorfactories.Here,theby.css()locatorfactoryperformsananalogueofdocument.querySelector().

Tip

TheentireProtractorAPIcanbefoundathttp://www.protractortest.org/#/api.

Thereasonforwritingtheteststhiswaymayfeelstrangetoyou.Afterall,it'sarealbrowserrunningarealapplication,soitmightmakesensetoreachforDOMmethodsandthelike.

ThereasonforusingtheProtractorAPIinsteadissimple:thetestcodeyouarewritingisnotbeingexecutedinsidethebrowserruntime.Instead,ProtractorishandingofftheseinstructionstoSelenium,whichinturnwillexecutetheminsidethebrowserandreturntheresults.Thus,thetestcodeyouwritecanonlyindirectlyinterfacewiththebrowserandtheDOM.

Page 575: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Thepurposeofthisrecipewastoassembleaverysimpleend-to-endtestsuitesothatyoucangetafeelofwhatgoesonbehindthescenesinsomeform.Whiletheteststhemselveswillappearmoreorlessastheydohere,regardlessofthetestinfrastructuretheyarerunningon,theinfrastructureitselfisfarfrombeingoptimal;anumberofchangesandadditionscouldbemadetomakeitmorerobust.

Whenrunningunittests,itisoftenusefulfortheunitteststodetectthechangesinfilesandrunthemagainimmediately.Alargepartofthisisbecauseunittestsshouldbeverylightweight.Anydependenciesontherestoftheapplicationaremockedorabstractedawaysothataminimalamountofcodecanberuntoprepareyourunittestenvironment.Thus,thereislittlecosttorunningasuiteofunittestsinasequence.

End-to-endtests,ontheotherhand,behaveintheoppositeway.Theydoindeedrequiretheentireapplicationtobeconstructedandrun,whichcanbecomputationallyexpensive.Pagenavigations,resettingtheentireapplication,initializingandclearingauthentication,andotheroperationsthatmightcommonlybeperformedinanend-to-endtestcantakealongtime.Therefore,itdoesn'tmakeasmuchsenseheretoruntheend-to-endtestswithafilewatcherobservingforchangesmadetothetests.

Page 576: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoCreatingaminimumviableunittestsuitewithKarma,Jasmine,andTypeScriptgivesyouagentleintroductiontounittestswithTypeScript

Page 577: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UnittestingasynchronousserviceAngular2servicetypesareessentiallyclassesdesignatedforinjectability.Theyareeasytotestsinceyouhaveagreatdealofcontroloverhowandwheretheyareprovided,andconsequently,howmanyinstancesyou'llbeabletocreate.Therefore,testsforserviceswillexistlargelyastheywouldforanynormalTypeScriptclass.

It'llbebetterifyouarefamiliarwiththecontentofthefirstfewrecipesofthischapterbeforeyouproceedfurther.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/3107.

Page 578: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyouwanttobuilda"magiceightball"service.Beginwiththefollowingcode,withaddedcommentsforclarity:

[src/app/magic-eight-ball.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassMagicEightBallService{

privatevalues:Array<string>;

privatelastIndex:number;

constructor(){

//Initializethevaluesarray

//Musthaveatleasttwoentries

this.values=[

'Askagainlater',

'Outlookgood',

'Mostlikely',

'Don'tcountonit'

];

//Initializewithanyvalidindex

this.lastIndex=this.getIndex();

}

privategetIndex():number{

//Returnarandomindexforthis.values

returnMath.floor(Math.random()*this.values.length);

}

reveal():string{

//Generateanewindex

letnewIdx=this.getIndex();

.

//Checkiftheindexwasthesameoneusedlasttime

if(newIdx===this.lastIndex){

//Ifso,shiftupone(wrappingaround)inthearray

//Thisisstillrandombehavior

newIdx=(++newIdx)%this.values.length;

}

//Savetheindexthatyouarenowusing

this.lastIndex=newIdx;

//Accessthestringandreturnit

returnthis.values[newIdx];

}

}

Page 579: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Thereareseveralthingstonoteabouthowthisservicebehaves:

ThisservicehasseveralprivatemembersbutonlyonepublicmembermethodTheserviceisrandomlyselectedfromanarrayTheserviceshouldn'treturnthesamevaluetwiceinarow

Thewayyourunittestsarewrittenshouldaccountfortheseaswellascompletelytestthebehaviorofthisservice.

Page 580: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginbycreatingtheframeworkofyourtestfile:

[src/app/magic-eight-ball.service.spec.ts]

import{TestBed}from'@angular/core/testing';

import{MagicEightBallService}from

'./magic-eight-ball.service';

describe('Service:MagicEightBall',()=>{

beforeEach(()=>{

TestBed.configureTestingModule({

providers:[

MagicEightBallService

]

});

});

});

Sofar,noneofthisshouldsurpriseyou.MagicEightBallServiceisaninjectable;itneedstobeprovidedinsideamoduledeclaration,whichisdonehere.However,toactuallyuseitinsideaunittest,youneedtoperformaformalinjectionsincethisiswhatwouldberequiredtoaccessitfrominsideacomponent.Thiscanbeaccomplishedwithinject:

[src/app/magic-eight-ball.service.spec.ts]

import{TestBed,inject}from'@angular/core/testing';

import{MagicEightBallService}from

'./magic-eight-ball.service';

describe('Service:MagicEightBall',()=>{

beforeEach(()=>{

TestBed.configureTestingModule({

providers:[

MagicEightBallService

]

});

});

it('shouldbeabletobeinjected',inject([MagicEightBallService],

(magicEightBallService:MagicEightBallService)=>{

expect(magicEightBallService).toBeTruthy();

})

);

});

Offtoagoodstart,butthisdoesn'tactuallytestanythingaboutwhattheserviceisdoing.Next,writeatestthatensuresthatastringofnon-zerolengthisbeingreturned:

[src/app/magic-eight-ball.service.spec.ts]

Page 581: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

import{TestBed,inject}from'@angular/core/testing';

import{MagicEightBallService}from

'./magic-eight-ball.service';

describe('Service:MagicEightBall',()=>{

beforeEach(()=>{

TestBed.configureTestingModule({

providers:[

MagicEightBallService

]

});

});

it('shouldbeabletobeinjected',inject([MagicEightBallService],

(magicEightBallService:MagicEightBallService)=>{

expect(magicEightBallService).toBeTruthy();

})

);

it('shouldreturnastringwithnonzerolength',

inject([MagicEightBallService],

(magicEightBallService:MagicEightBallService)=>{

letresult=magicEightBallService.reveal();

expect(result).toEqual(jasmine.any(String));

expect(result.length).toBeGreaterThan(0);

})

);

});

Finally,youshouldwriteatesttoensurethatthetwovaluesreturnedarenotthesame.Sincethismethodisrandom,youcanrunituntilyouareblueinthefaceandstillnotbetotallysure.However,checkingthis50timesinarowisafinewaytobefairlycertain:

[src/app/magic-eight-ball.service.spec.ts]

import{TestBed,inject}from'@angular/core/testing';

import{MagicEightBallService}from

'./magic-eight-ball.service';

describe('Service:MagicEightBall',()=>{

beforeEach(()=>{

TestBed.configureTestingModule({

providers:[

MagicEightBallService

]

});

});

it('shouldbeabletobeinjected',inject([MagicEightBallService],

(magicEightBallService:MagicEightBallService)=>{

expect(magicEightBallService).toBeTruthy();

Page 582: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

})

);

it('shouldreturnastringwithnonzerolength',

inject([MagicEightBallService],

(magicEightBallService:MagicEightBallService)=>{

letresult=magicEightBallService.reveal();

expect(result).toEqual(jasmine.any(String));

expect(result.length).toBeGreaterThan(0);

})

);

it('shouldnotreturnthesamevaluetwiceinarow',

inject([MagicEightBallService],

(magicEightBallService:MagicEightBallService)=>{

letlast;

for(leti=0;i<50;++i){

letnext=magicEightBallService.reveal();

expect(next).not.toEqual(last);

last=next;

}

})

);

});

Terrific!Allthesetestshavepassed;you'vedoneagoodjobbuildingsomeincrementalanddescriptivecodecoverageforyourservice.

Page 583: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Theinjecttestfunctionperformsdependencyinjectionforyoueachtimeitisinvoked,usingthearrayofinjectableclassespassedasthefirstargument.Thearrowfunctionthatispassedasitssecondargumentwillbehaveinessentiallythesamewayasacomponentconstructor,whereyouareabletousethemagicEightBallServiceparameterasaninstanceoftheservice.

Tip

Oneimportantdifferencefromhowitisinjectedcomparedtoacomponentconstructoristhatinsideacomponentconstructor,youwouldbeabletousethis.magicEightBallServicerightaway.Withrespecttoinjectionintounittests,itdoesnotautomaticallyattachtothis.

Page 584: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Importantconsiderationsforunittestingarewhattestsshouldbewrittenandhowtheyshouldproceed.Respectingtheboundariesofpublicandprivatemembersisessential.Sincethesetestsarewritteninawaythatonlyutilizesthepublicmembersoftheservice,theauthorisfreetogoaboutchanging,extending,orrefactoringtheinternalsoftheservicewithoutworryingaboutbreakingorneedingtoupdatethetests.Awell-designedclasswillbefullytestablefromitspublicinterface.

Tip

Thisnotionbringsupaninterestingphilosophicalpointregardingunittesting.Youshouldbeabletodescribethebehaviorofawell-formedserviceasafunctionofitspublicmembers.Similarly,awell-formedserviceshouldthenberelativelyeasytowriteunittests,giventhattheformerstatementistrue.

Ifitisthenthecasethatyoufindyourunittestsaredifficulttowrite—forexample,youareneedingtoreachintoaprivatememberoftheservicetotestitproperly—thenconsiderthenotionthatyourservicemightnotbeaswelldesignedasitcouldbe.

Inshort,ifit'shardtotest,thenyoumighthavewrittenaclassinaweirdway.

Testingwithoutinjection

Anobservantdeveloperwillnoteherethattheserviceyouaretestingdoesn'thaveanymeaningfuldependenceoninjection.Injectingitintovariousplacesintheapplicationsurelyprovidesitwithaconsistentway,buttheservicedefinitioniswhollyunawareofthisfact.Afterall,instantiationisinstantiation,andthisservicedoesn'tappeartobemorethananinjectableclass.Therefore,itiscertainlypossibletonotbotherinjectingtheserviceatallandmerelyinstantiatingitusingthenewkeyword:

[src/app/magic-eight-ball.service.spec.ts]

import{MagicEightBallService}from

'./magic-eight-ball.service';

describe('Service:MagicEightBall',()=>{

letmagicEightBallService;

beforeEach(()=>{

magicEightBallService=newMagicEightBallService();

});

it('shouldbeabletobeinjected',()=>{

expect(magicEightBallService).toBeTruthy();

});

Page 585: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

it('shouldreturnastringwithnonzerolength',()=>{

letresult=magicEightBallService.reveal();

expect(result).toEqual(jasmine.any(String));

expect(result.length).toBeGreaterThan(0);

});

it('shouldnotreturnthesamevaluetwiceinarow',()=>{

letlast;

for(leti=0;i<50;++i){

letnext=magicEightBallService.reveal();

expect(next).not.toEqual(last);

last=next;

}

});

});

Ofcourse,thisrequiresthatyoukeeptrackofwhethertheservicecaresaboutwhetherornotithasbeeninjectedanywhere.

Page 586: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnittestingacomponentwithaservicedependencyusingstubsshowshowyoucancreateaservicemocktowriteunittestsandavoiddirectdependenciesUnittestingacomponentwithaservicedependencyusingspiesshowshowyoucankeeptrackofservicemethodinvocationsinsideaunittest

Page 587: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UnittestingacomponentwithaservicedependencyusingstubsStandalonecomponenttestingiseasy,butyouwillrarelyneedtowritemeaningfultestsforacomponentthatexistsinisolation.Moreoftenthannot,thecomponentwillhaveoneormanydependencies,andwritinggoodunittestsisthedifferencebetweendelightanddespair.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/6651/.

Page 588: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoualreadyhavetheservicefromtheUnittestingasynchronousservicerecipe.Inaddition,youhaveacomponent,whichmakesuseofthisservice:

[src/app/magic-eight-ball/magic-eight-ball.component.ts]

import{Component}from'@angular/core';

import{MagicEightBallService}from

'../magic-eight-ball.service';

@Component({

selector:'app-magic-eight-ball',

template:`

<button(click)="update()">Clickme!</button>

<h1>{{result}}</h1>

`

})

exportclassMagicEightBallComponent{

result:string='';

constructor(privatemagicEightBallService_:MagicEightBallService){}

update(){

this.result=this.magicEightBallService_.reveal();

}

}

Yourobjectiveistowriteasuiteofunittestsforthiscomponentwithoutsettinganexplicitdependencyontheservice.

Page 589: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Beginwithaskeletonofyourtestfile:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

describe('Component:MagicEightBall',()=>{

beforeEach(async(()=>{

}));

afterEach(()=>{

});

it('shouldbeginwithnotext',async(()=>{

}));

it('shouldshowtextafterclick',async(()=>{

}));

});

You'llfirstwanttoconfigurethetestmodulesothatitproperlyprovidestheseimportedtargetsinthetest:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

describe('Component:MagicEightBall',()=>{

letfixture;

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

MagicEightBallService

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

}));

Page 590: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

afterEach(()=>{

fixture=undefined;

});

it('shouldbeginwithnotext',async(()=>{

}));

it('shouldshowtextafterclick',async(()=>{

}));

});

Stubbingaservicedependency

Injectingtheactualserviceworksjustfine,butthisisn'twhatyouwanttodo.Youdon'twanttoactuallyinjectaninstanceofMagicEightBallServiceintothecomponent,asthatwouldsetadependencyontheserviceandmaketheunittestmorecomplicatedthanitneedstobe.However,MagicEightBallComponentneedstoimportsomethingthatresemblesaMagicEightBallService.Anexcellentsolutionhereistocreateaservicestubandinjectitinitsplace:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

describe('Component:MagicEightBall',()=>{

letfixture;

letmagicEightBallResponse='Answerunclear';

letmagicEightBallServiceStub={

reveal:()=>magicEightBallResponse

};

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

{

provide:MagicEightBallService,

useValue:magicEightBallServiceStub

}

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

}));

afterEach(()=>{

fixture=undefined;

Page 591: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

});

it('shouldbeginwithnotext',async(()=>{

}));

it('shouldshowtextafterclick',async(()=>{

}));

});

Acomponentcan'ttellthedifferencebetweentheactualserviceanditsmock,soitwillbehavenormallyinthetestconditionsyou'vesetup.

Next,youshouldwritethepreclicktestbycheckingthatthefixture'snativeElementcontainsnotext:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

describe('Component:MagicEightBall',()=>{

letfixture;

letgetHeaderEl=()=>

fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

letmagicEightBallServiceStub={

reveal:()=>magicEightBallResponse

};

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

{

provide:MagicEightBallService,

useValue:magicEightBallServiceStub

}

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldbeginwithnotext',async(()=>{

Page 592: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

fixture.detectChanges();

expect(getHeaderEl().textContent).toEqual('');

}));

it('shouldshowtextafterclick',async(()=>{

}));

});

Triggeringeventsinsidethecomponentfixture

Forthesecondtest,youshouldtriggeraclickonthebutton,instructthefixturetoperformchangedetection,andtheninspecttheDOMtoseethatthetextwasproperlyinserted.Sinceyouhavedefinedthetextthatthestubwillreturn,youcanjustcompareitdirectlywiththat:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

import{By}from'@angular/platform-browser';

describe('Component:MagicEightBall',()=>{

letfixture;

letgetHeaderEl=()=>

fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

letmagicEightBallServiceStub={

reveal:()=>magicEightBallResponse

};

...

it('shouldbeginwithnotext',async(()=>{

expect(getHeaderEl().textContent).toEqual('');

}));

it('shouldshowtextafterclick',async(()=>{

fixture.debugElement.query(By.css('button'))

.triggerEventHandler('click',null);

fixture.detectChanges();

expect(getHeaderEl().textContent)

.toEqual(magicEightBallResponse);

}));

});

You'llnotethatthisneedstoimportandusetheBy.csspredicate,whichisrequiredtoperformDebugElementinspections.

Page 593: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Asdemonstratedinthedependencyinjectionchapter,providingastubtothecomponentisnodifferentthanprovidingaregularvaluetothecoreapplication.

Thestubhereisasinglefunctionthatreturnsastaticvalue.Thereisnoconceptofrandomlyselectingfromtheservice'sarrayofstrings,andtheredoesn'tneedtobe.Theunittestsfortheserviceitselfensurethatitisbehavingproperly.Instead,theonlyvalueprovidedbytheservicehereistheinformationitpassesbacktothecomponentforinterpolationbackintothetemplate.

Page 594: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoWritingaminimumviableunittestsuiteforasimplecomponentshowsyouabasicexampleofunittestingAngular2componentsUnittestingasynchronousservicedemonstrateshowinjectionismockedinunittestsUnittestingacomponentwithaservicedependencyusingspiesshowshowyoucankeeptrackofservicemethodinvocationsinsideaunittest

Page 595: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UnittestingacomponentwithaservicedependencyusingspiesTheabilitytostuboutservicesisuseful,butitcanbelimitinginanumberofways.Itcanalsobetedious,asthestubsyoucreatemustremainuptodatewiththepublicinterfaceoftheservice.Anotherexcellenttoolatyourdisposalwhenwritingunittestsisthespy.

Aspyallowsyoutoselectafunctionormethod.Italsohelpsyoucollectinformationaboutifandhowitwasinvokedaswellashowitwillbehaveonceitisinvoked.Itissimilarinconcepttoastubbutallowsyoutohaveamuchmorerobustunittest.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/3444/.

Page 596: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththecomponenttestsyouwroteinthelastrecipe:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

import{By}from'@angular/platform-browser';

describe('Component:MagicEightBall',()=>{

letfixture;

letgetHeaderEl=()=>fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

letmagicEightBallServiceStub={

reveal:()=>magicEightBallResponse

};

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

{

provide:MagicEightBallService,

useValue:magicEightBallServiceStub

}

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldbeginwithnotext',async(()=>{

fixture.detectChanges();

expect(getHeaderEl().textContent).toEqual('');

}));

it('shouldshowtextafterclick',async(()=>{

fixture.debugElement.query(By.css('button'))

.triggerEventHandler('click',null);

fixture.detectChanges();

expect(getHeaderEl().textContent)

.toEqual(magicEightBallResponse);

}));

Page 597: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

});

Page 598: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Insteadofusingastub,configurethetestmoduletoprovidetheactualservice:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

import{By}from'@angular/platform-browser';

describe('Component:MagicEightBall',()=>{

letfixture;

letgetHeaderEl=()=>fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

MagicEightBallService

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

}));

afterEach(()=>{

fixture=undefined;

});

it('shouldbeginwithnotext',async(()=>{

fixture.detectChanges();

expect(getHeaderEl().textContent).toEqual('');

}));

it('shouldshowtextafterclick',async(()=>{

fixture.debugElement.query(By.css('button'))

.triggerEventHandler('click',null);

fixture.detectChanges();

expect(getHeaderEl().textContent)

.toEqual(magicEightBallResponse);

}));

});

Settingaspyontheinjectedservice

Yourgoalistouseamethodspytointerceptcallstoreveal()ontheservice.Theproblemhere,however,isthattheserviceisbeinginjectedintothecomponent;therefore,youdon'thavea

Page 599: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

directabilitytogetareferencetotheserviceinstanceandsetaspyonit.Fortunately,thecomponentfixtureprovidesthisforyou:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

import{By}from'@angular/platform-browser';

describe('Component:MagicEightBall',()=>{

letfixture;

letgetHeaderEl=()=>fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

letmagicEightBallService;

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

MagicEightBallService

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

magicEightBallService=fixture.debugElement.injector

.get(MagicEightBallService);

}));

afterEach(()=>{

fixture=undefined;

magicEightBallService=undefined;

});

...

});

Next,setaspyontheserviceinstanceusingspyOn().Configurethespytointerceptthemethodcallandreturnthestaticvalueinstead:

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

import{By}from'@angular/platform-browser';

describe('Component:MagicEightBall',()=>{

Page 600: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

letfixture;

letgetHeaderEl=()=>fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

letmagicEightBallService;

letrevealSpy;

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

MagicEightBallService

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

magicEightBallService=fixture.debugElement.injector

.get(MagicEightBallService);

revealSpy=spyOn(magicEightBallService,'reveal')

.and.returnValue(magicEightBallResponse);

}));

afterEach(()=>{

fixture=undefined;

magicEightBallService=undefined;

revealSpy=undefined;

});

...

});

Withthisspy,youarenowcapableofseeinghowtherestoftheapplicationinteractswiththiscapturedmethod.Addanewtest,andcheckthatthemethodiscalledonceandreturnsthepropervalueafteraclick(thisalsopullstheclickingactionintoitsowntesthelper):

[src/app/magic-eight-ball/magic-eight-ball.component.spec.ts]

import{TestBed,async}from'@angular/core/testing';

import{MagicEightBallComponent}from

'./magic-eight-ball.component';

import{MagicEightBallService}from

'../magic-eight-ball.service';

import{By}from'@angular/platform-browser';

describe('Component:MagicEightBall',()=>{

letfixture;

letgetHeaderEl=()=>fixture.nativeElement.querySelector('h1');

letmagicEightBallResponse='Answerunclear';

letmagicEightBallService;

letrevealSpy;

letclickButton=()=>{

fixture.debugElement.query(By.css('button'))

.triggerEventHandler('click',null);

Page 601: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

};

beforeEach(async(()=>{

TestBed.configureTestingModule({

declarations:[

MagicEightBallComponent

],

providers:[

MagicEightBallService

]

});

fixture=TestBed.createComponent(MagicEightBallComponent);

magicEightBallService=fixture.debugElement.injector

.get(MagicEightBallService);

revealSpy=spyOn(magicEightBallService,'reveal')

.and.returnValue(magicEightBallResponse);

}));

afterEach(()=>{

fixture=undefined;

magicEightBallService=undefined;

revealSpy=undefined;

});

it('shouldbeginwithnotext',async(()=>{

fixture.detectChanges();

expect(getHeaderEl().textContent).toEqual('');

}));

it('shouldcallrevealafteraclick',async(()=>{

clickButton();

expect(revealSpy.calls.count()).toBe(1);

expect(revealSpy.calls.mostRecent().returnValue)

.toBe(magicEightBallResponse);

}));

it('shouldshowtextafterclick',async(()=>{

clickButton();

fixture.detectChanges();

expect(getHeaderEl().textContent)

.toEqual(magicEightBallResponse);

}));

});

Note

NotethatdetectChanges()isonlyrequiredtoresolvethedatabinding,nottoexecuteeventhandlers.

Page 602: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Jasminespiesactasmethodinterceptorsandarecapableofinspectingeverythingaboutthegivenmethodinvocation.Itcantrackifandwhenamethodwascalled,whatargumentsitwascalledwith,howmanytimesitwascalled,howitshouldbehave,andsoon.Thisisextremelyusefulwhentryingtoremovedependenciesfromcomponentunittests,asyoucanmockoutthepublicinterfaceoftheserviceusingspies.

Page 603: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Spiesarenotbeholdentoreplacethemethodoutright.Here,itisusefultobeabletopreventtheexecutionfromreachingtheinternalsoftheservice,butitisnotdifficulttoimaginecaseswhereyouwouldonlywanttopassivelyobservetheinvocationofacertainmethodandallowtheexecutiontocontinuenormally.

Forsuchapurpose,insteadofusing.and.returnValue(),Jasmineallowsyoutouse.and.callThrough(),whichwillallowtheexecutiontoproceeduninterrupted.

Page 604: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoWritingaminimumviableunittestsuiteforasimplecomponentshowsyouabasicexampleofunittestingAngular2componentsUnittestingasynchronousservicedemonstrateshowinjectionismockedinunittestsUnittestingacomponentwithaservicedependencyusingstubsshowshowyoucancreateaservicemocktowriteunittestsandavoiddirectdependencies

Page 605: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Chapter10.PerformanceandAdvancedConceptsThischapterwillcoverthefollowingrecipes:

UnderstandingandproperlyutilizingenableProdModewithpureandimpurepipesWorkingwithzonesoutsideAngularListeningforNgZoneeventsExecutionoutsidetheAngularzoneConfiguringcomponentstouseexplicitchangedetectionwithOnPushConfiguringViewEncapsulationformaximumefficiencyConfiguringtheAngular2RendererServicetousewebworkersConfiguringapplicationstouseahead-of-timecompilationConfiguringanapplicationtouselazyloading

Page 606: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IntroductionAngular2wasatotalrebuildforanumberofreasons,butoneofthebiggestoneswascertainlyefficiency.Theframeworkthatemergedfromtheashesisasleekandelegantone,butnotwithoutitscomplexities.

Thischapterservestoexploresomeofthenewfeaturesthatitbuildsuponandhowtomosteffectivelyemploythemtostreamlineyourapplication.

Page 607: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

UnderstandingandproperlyutilizingenableProdModewithpureandimpurepipesAngular2'schangedetectionprocessisanelegantbutficklebeastthatischallengingtounderstandatfirst.Whileitoffershugeefficiencygainsoverthe1.xframework,thegainscancomeatacost.ThedevelopmentmodeofAngularisactivatedbydefault,whichwillalertyouwhenyourcodeisindangerofbehavinginawaythatdefeatsthechangedetectionefficiencygains.Inthisrecipe,you'llimplementafeaturethatviolatesAngular'schangedetectionschema,correctit,andsafelyuseenableProdMode.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/0623/.

Page 608: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwithasimplecomponent:

[src/app/app.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-root',

template:`

<input#t>

<button(click)="update(t.value)">Save</button>

<h1>{{title}}</h1>

`

})

exportclassAppComponent{

title:string='';

update(newTitle:string){

this.title=newTitle;

}

}

Whenthebuttoninthiscomponentisclicked,itgrabsthevalueoftheinputandinterpolatesittotheheadertag.Asis,thisimplementationisperfectlysuitable.

Page 609: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TodemonstratetherelevanceofenableProdMode,you'llneedtointroduceabehaviorthatenableProdModewouldmask.Morespecifically,thismeansapieceofyourapplicationwillbehavedifferentlywhentwosequentialpassesofchangedetectionarerun.

Note

Thereareasignificantnumberofwaystoimplementthis,butforthepurposeofthisrecipe,you'llimplementanonsensicalpipethatchangeseverytimeit'sused.

Generatingaconsistencyerror

Createanonsensicalpipe,namelyAddRandomPipe:

[src/app/add-random.pipe.ts]

import{Pipe,PipeTransform}from'@angular/core';

@Pipe({

name:'addRandomPipe'

})

exportclassAddRandomPipeimplementsPipeTransform{

transform(value:string):string{

returnvalue+Math.random();

}

}

Next,takethispipeandintroduceittoyourcomponent:

[src/app/app.component.ts]

import{Component}from'@angular/core';

import{AddRandomPipe}from'./add-random.pipe';

@Component({

selector:'app-root',

template:`

<input#t>

<button(click)="update(t.value)">Save</button>

<h1>{{title|addRandomPipe}}</h1>

`

})

exportclassAppComponent{

title:string='';

update(newTitle:string){

this.title=newTitle;

}

}

Page 610: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Thiswon'tcreateanerroryet,though.

Note

Angularwillindeedrunthechangedetectionprocesstwice,soitmightseemmysteriousthatitdoesn'tcreateanerror.Eventhoughthepipewillgenerateanewoutputeverytimeitstransformmethodisinvoked,Angularissmartenoughtorecognizethattheinputoftheinterpolationaren'tchanging,andtherefore,reevaluatingthepipeisunnecessary.Thisiscalleda"pure"pipe,whichistheAngulardefault.

InordertogetAngulartoevaluatethepipeduringeachchangedetectioncycle,specifythepipeas"impure":

[src/app/add-random.pipe.ts]

import{Pipe,PipeTransform}from'@angular/core';

@Pipe({

name:'addRandomPipe',

pure:false

})

exportclassAddRandomPipeimplementsPipeTransform{

transform(value:string):string{

returnvalue+Math.random();

}

}

Nowthefunbegins.Whenyouruntheapplication,youshouldseesomethingsimilartothefollowingerrormessage:

EXCEPTION:Errorin./AppComponentclassAppComponent-inlinetemplate:3:8

causedby:Expressionhaschangedafteritwaschecked.Previousvalue:

'0.0495151713435904'.Currentvalue:'0.9266277919907477'.

Introducingchangedetectioncompliance

Theerrorisbeingthrownassoonastheapplicationstartsup,beforeyou'reevengivenachancetopressthebutton.ThismeansthatAngularisdetectingthebindingmismatchasthecomponentisbeinginstantiatedandrenderedforthefirsttime.

Note

You'vecreatedapipethatintentionallychangeseachtime,soyourgoalistoinstructAngulartonotbothercheckingthepipeoutputtwiceagainstitselfuponcomponentinstantiation.

Atthispoint,you'vemanagedtogetAngulartothrowaconsistencyerror,whichitdoesbecauseit'srunningchangedetectioncheckstwiceandgettingdifferentresults.SwitchingonenableProdModeatthispointwouldstoptheerrorsinceAngularwouldthenonlyrunchange

Page 611: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

detectiononceandnotbothertocheckforconsistency.ThisisbecauseittrustsyoutohaveverifiedcompliancebeforeusingenableProdMode.TurningonenableProdModetomaskconsistencyerrormessagesisabitlikecominghometofindyourhouseisonfireandthendecidingtogoonavacation.

Angularallowsyoutocontrolthisbyspecifyingthechangedetectionstrategyforthecomponent.Thedefaultistoalwaysperformachangedetectioncheck,butthiscanbeoverriddenwiththeOnPushconfiguration:

[src/app/app.component.ts]

import{Component,ChangeDetectionStrategy}

from'@angular/core';

import{AddRandomPipe}from'./add-random.pipe';

@Component({

selector:'app-root',

template:`

<input#t>

<button(click)="update(t.value)">Save</button>

<h1>{{title|addRandomPipe}}</h1>

`,

changeDetection:ChangeDetectionStrategy.OnPush

})

exportclassAppComponent{

title:string='';

update(newTitle:string){

this.title=newTitle;

}

}

Nowwhenthecomponentinstanceisinitialized,youshouldnolongerseetheconsistencyerror.

SwitchingonenableProdMode

SinceyourapplicationisnowcompliantwithAngular'schangedetectionmechanism,you'refreetouseenableProdMode.Thisletsyourapplicationrunchangedetectiononceeachtime.Thisisbecauseitassumestheapplicationwillarriveinastablestate.

Inyourapplication'sbootstrappingfile,invokeenableProdModebeforeyoustartbootstrapping:

[src/main.ts]

import{platformBrowserDynamic}

from'@angular/platform-browser-dynamic';

import{enableProdMode}from'@angular/core';

import{AppModule}from'./app/';

enableProdMode();

Page 612: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

platformBrowserDynamic().bootstrapModule(AppModule);

Page 613: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...enableProdModewillconfigureyourapplicationinanumberofways,includingsilencingerrorandinformationalerrormessages,amongothers;however,noneofthesewaysisasvisibleasthesuppressionofthesecondarychangedetectionprocess.

Page 614: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Thereareotherwaystomitigatethisconsistencyproblem.Forexample,supposeyouwanttogenerateapipethatwillappendarandomnumbertotheinput.Itdoesn'tnecessarilyneedtobeadifferentrandomnumbereverysingletime;rather,youcanhaveoneforeachuniqueinputwithinacertainperiodoftime.Suchasituationcouldallowyoutohaveapipethatutilizessomesortofcachingstrategy.Ifthepipecachesresultsforaperiodoftimelongerthanchangedetectiontakestocomplete(whichisnotverylong),thenalteringthechangedetectionstrategyofthecomponentisnotnecessary.Thisisbecausemultiplepipeinvocationswillyieldanidenticalresponse.Forexample,refertothefollowingcode:

[src/app/add-random.pipe.ts]

import{Pipe,PipeTransform}from'@angular/core';

@Pipe({

name:'addRandomPipe',

pure:false

})

exportclassAddRandomPipeimplementsPipeTransform{

cache:Object={};

transform(input:string):string{

letvalue=this.cache[input];

if(!value||value.expire<Date.now()){

value={

text:input+Math.random(),

//Expiresinonesecond

expire:Date.now()+1000

}

this.cache[input]=value;

}

returnvalue.text;

}

}

Withthis,youcansafelystrikechangeDetection:ChangeDetectionStrategy.OnPushfromComponentMetadataofyourAppComponentandyouwillnotseeanyconsistencyerrors.

Page 615: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoConfiguringtheAngular2rendererservicetousewebworkersguidesyouthroughtheprocessofsettingupyourapplicationtorenderonawebworkerConfiguringapplicationstouseahead-of-timecompilationguidesyouthroughhowtocompileanapplicationduringthebuild

Page 616: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

WorkingwithzonesoutsideAngularWorkingwithzonesentirelyinsideoftheAngularframeworkconcealswhattheyarereallydoingbehindthescenes.Itwouldbeadisservicetoyou,thereader,tojustglossovertheunderlyingmechanism.Inthisrecipe,you'lltakethevanillazone.jsimplementationoutsideofAngularandmodifyitabitinordertoseehowAngularcanmakeuseofit.

TherewillbenoAngularusedinthisrecipe,onlyzone.jsinsideasimpleHTMLfile.Furthermore,thisrecipewillbewritteninplainES5JavaScriptforsimplicity.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/0591/.

Page 617: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththefollowingsimpleHTMLfile:

[index.html]

<buttonid="button">Clickme</button>

<buttonid="add">Addlistener</button>

<buttonid="remove">Removelistener</button>

<script>

varbutton=document.getElementById('button');

varadd=document.getElementById('add');

varremove=document.getElementById('remove');

varclickCallback=function(){

console.log('click!');

};

setInterval(function(){

console.log('setinterval!');

},1000);

add.addEventListener('click',function(){

button.addEventListener('click',clickCallback);

});

remove.addEventListener('click',function(){

button.removeEventListener('click',clickCallback);

});

</script>

Eachofthesecallbackshaslogstatementsinsidethemsoyoucanseewhentheyareinvoked:

setIntervalcallsitsassociatedlistenereverysecondClickingonClickmecallsitslistenerifitisattachedClickingonAddlistenerattachesaclicklistenertothebuttonClickingonRemovelistenerremovestheclicklistener

Note

There'snothingspecialgoingonhere,becauseallofthesearedefaultbrowserAPIs.Themagicofzones,asyouwillsee,isthatthezonebehaviorcanbeintroducedaroundthiswithoutmodifyinganycode.

Page 618: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...First,addthezone.jsscripttothetopofthefile:

[index.html]

<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.26/zone.js">

</script>

<buttonid="button">Clickme</button>

<buttonid="add">Addlistener</button>

<buttonid="remove">Removelistener</button>

...

Note

There'snospecialsetupneededforzone.js,butitneedstobeaddedbeforeyousetlistenersordoanythingthatcouldhaveasynchronousimplications.Angularneedsthisdependencytobeaddedbeforeitisinitialized.

AddingthisscriptintroducesZonetotheglobalnamespace.zone.jshasalreadycreatedaglobalzoneforyou.Thiscanbeaccessedwiththefollowing:

Zone.current

Forkingazone

Theglobalzoneisn'tdoinganythinginterestingyet.Tocustomizeazone,you'llneedtocreateyourownbyforkingtheonewehaveandrunningrelevantcodeinsideit.Dothisasfollows:

[index.html]

<script

src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.26/zone.js">

</script>

<buttonid="button">Clickme</button>

<buttonid="add">Addlistener</button>

<buttonid="remove">Removelistener</button>

<script>

varbutton=document.getElementById('button');

varadd=document.getElementById('add');

varremove=document.getElementById('remove');

Zone.current.fork({}).run(function(){

varclickCallback=function(){

console.log('click!');

};

Page 619: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

setInterval(function(){

console.log('setinterval!');

},1000);

add.addEventListener('click',function(){

button.addEventListener('click',clickCallback);

});

remove.addEventListener('click',function(){

button.removeEventListener('click',clickCallback);

});

});

</script>

Behaviorally,thisdoesn'tchangeanythingfromtheperspectiveoftheconsole.fork()takesanemptyZoneSpecobjectliteral,whichyouwillmodifynext.

OverridingzoneeventswithZoneSpec

Whenapieceofcodeisruninazone,youareabletoattachbehaviorsatimportantpointsintheasynchronousbehaviorflow.Here,you'lloverridefourzoneevents:

scheduleTask

invokeTask

hasTask

cancelTask

You'llbeginwithscheduleTask.DefineanoverridemethodinsideZoneSpec.Overridesusetheeventnamesprefixedwithon:

[index.html]

<script

src="https://cdnjs.cloudflare.com/ajax/libs/zone.js/0.6.26/zone.js">

</script>

<buttonid="button">Clickme</button>

<buttonid="add">Addlistener</button>

<buttonid="remove">Removelistener</button>

<script>

varbutton=document.getElementById('button');

varadd=document.getElementById('add');

varremove=document.getElementById('remove');

Zone.current.fork({

onScheduleTask:function(zoneDelegate,zone,targetZone,task){

console.log('schedule');

zoneDelegate

.scheduleTask(targetZone,task);

}

Page 620: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

}).run(function(){

varclickCallback=function(){

console.log('click!');

};

setInterval(function(){

console.log('setinterval!');

},1000);

add.addEventListener('click',function(){

button.addEventListener('click',clickCallback);

});

remove.addEventListener('click',function(){

button.removeEventListener('click',clickCallback);

});

});

</script>

Withthisaddition,youshouldseethatthezonerecognizesthatthreetasksarebeingscheduled.Thisshouldmakesense,asyouaredeclaringthreeinstancesthatcangenerateasynchronousactions:setInterval,addEventListener,andremoveEventListener.IfyouclickonAddlistener,you'llseeitschedulethefourthtaskaswell.

Note

zoneDelegate.scheduleTask()isrequiredbecauseyouareactuallyoverwritingwhatthezoneisusingforthathandler.Ifyoudon'tperformthisaction,theschedulerhandlerwillexitbeforeactuallyschedulingthetask.

Theoppositeofschedulingataskiscancelingit,sooverridethateventhandlernext:

[index.html]

Zone.current.fork({

onScheduleTask:function(zoneDelegate,zone,targetZone,task){

console.log('schedule');

zoneDelegate

.scheduleTask(targetZone,task);

},

onCancelTask:function(zoneDelegate,zone,targetZone,task){

console.log('cancel');

zoneDelegate

.cancelTask(targetZone,task);

}

}).run(function(){

...

});

Acanceleventoccurswhenascheduledtaskisdestroyed,inthisexample,whenremoveEventListener()isinvoked.IfyouclickonAddlistenerandthenRemovelistener,

Page 621: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

you'llseeacanceleventoccur.

Theschedulingoftasksisvisibleduringthestartup,buteachtimeabuttonisclickedorasetIntervalhandlerisexecuted,youdon'tseeanythingbeinglogged.Thisisbecauseschedulingatask,whichoccursduringregistration,isdistinctfrominvokingatask,whichiswhentheasynchronouseventactuallyoccurs.

Todemonstratethis,addaninvokeTaskoverride:

[index.html]

Zone.current.fork({

onScheduleTask:function(zoneDelegate,zone,targetZone,task){

console.log('schedule');

zoneDelegate

.scheduleTask(targetZone,task);

},

onCancelTask:function(zoneDelegate,zone,targetZone,task){

console.log('cancel');

zoneDelegate

.cancelTask(targetZone,task);

},

onInvokeTask:function(zoneDelegate,zone,targetZone,task,

applyThis,applyArgs){

console.log('invoke');

zoneDelegate

.invokeTask(targetZone,task,applyThis,applyArgs);

}

}).run(function(){

...

});

Withthisaddition,youshouldnowbeabletoseeaconsolelogeachtimeataskisinvoked—forabuttonclickorasetIntervalcallback.

Sofar,you'vebeenabletoseewhenthezonehastasksscheduledandinvoked,butnow,dothereverseofthistodetectwhenallthetaskshavebeencompleted.ThiscanbeaccomplishedwithhasTask,whichcanalsobeoverridden:

[index.html]

Zone.current.fork({

onScheduleTask:function(zoneDelegate,zone,targetZone,task){

console.log('schedule');

zoneDelegate

.scheduleTask(targetZone,task);

},

onCancelTask:function(zoneDelegate,zone,targetZone,task){

console.log('cancel');

zoneDelegate

Page 622: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

.cancelTask(targetZone,task);

},

onInvokeTask:function(zoneDelegate,zone,targetZone,task,

applyThis,applyArgs){

console.log('invoke');

zoneDelegate

.invokeTask(targetZone,task,applyThis,applyArgs);

},

onHasTask:function(zoneDelegate,zone,targetZone,isEmpty){

console.log('has');

zoneDelegate.hasTask(targetZone,isEmpty);

}

}).run(function(){

...

});

TheisEmptyparameterofonHasTaskhasthreeproperties:eventTask,macroTask,andmicroTask.ThesethreepropertiesmaptoBooleansdescribingwhethertheassociatedqueueshaveanytasksinsidethem.

Withthesefourcallbacks,youhavesuccessfullyinterceptedfourimportantpointsinthecomponentlifecycle:

Whenatask"generator"isscheduled,whichmaygeneratetasksfrombrowserortimereventsWhenatask"generator"iscanceledWhenataskisactuallyinvokedHowtodeterminewhetheranytasksarescheduledandofwhattype

Page 623: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Theconceptthatformsthecoreofzonesistheinterceptionofasynchronoustasks.Moredirectly,youwanttheabilitytoknowwhenasynchronoustasksarebeingcreated,howmanythereare,andwhenthey'redone.

zone.jsaccomplishesthisbyshimmingalltherelevantbrowsermethodsthatareresponsibleforsettingupasynchronoustasks.Infact,allthemethodsusedinthismethod—setInterval,addEventListener,andremoveEventListener—areallshimmedsothatthezonetheyareruninsideisawareofanyasynchronoustaskstheymightaddtothequeue.

Page 624: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...TobegintorelatethistoAngular,you'llneedtotakeastepbacktoexaminethezoneecosystem.

Understandingzone.run()

You'llnoticeinthisexamplethatinvokeisprintedforeachasynchronousaction,evenforthosethatwereregisteredinsideanotherasynchronousaction.Thisisthepowerofzones.Anythingdoneinsidethezone.run()blockwillcascadewithinthesamezone.Thisway,thezonecankeeptrackofanunbrokensegmentofasynchronousbehaviorwithoutanoceanofboilerplatecode.

Microtasksandmacrotasks

Thisactuallyhasnothingtodowithzone.jsatallbutratherwithhowthebrowsereventloopworks.Alltheeventsgeneratedbyyouinthisexample—clicks,timerevents,andsoon—aremacrotasks.Thatis,thebrowserrespectstheirhandlersasasynchronous,blockingsegmentofcode.Thecodethatexecutesaroundthesetasks—thezone.jscallbacks,forexample—aremicrotasks.Theyaredistinctfrommacrotasksbutarestillsynchronouslyexecutedaspartoftheentire"turn"forthatmacrotask.

Note

Amacrotaskmaygeneratemoremicrotasksforitselfwithinitsownturn.

Oncethemicrotaskandmacrotaskqueuesareempty,thezonecanbeconsideredtobestable,sincethereisnoasynchronousbehaviortobeanticipated.ForAngular,thissoundslikeagreattimetoupdatetheUI.

Infact,thisisexactlywhatAngularisdoingbehindthescenes.Angularviewsthebrowserthroughthetask-centricgogglesofzone.jsandusesthisclevertooltodecidewhentogoaboutrendering.

Page 625: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoListeningforNgZoneeventsgivesabasicunderstandingofhowAngularisusingzonesExecutionoutsidetheAngularzoneshowsyouhowtoperformlong-runningoperationswithoutincurringazoneoverheadConfiguringcomponentstouseexplicitchangedetectionwithOnPushdescribeshowtomanuallycontrolAngular'schangedetectionprocess

Page 626: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ListeningforNgZoneeventsWiththeintroductionofAngular2comestheconceptofzones.Beforeyoubeginthisrecipe,IstronglyrecommendedyoutobeginbyworkingthroughtheWorkingwithzonesoutsideAngularrecipe.

Page 627: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

zone.jszone.jsisalibrarythatAngular2directlydependsupon.ItallowsAngulartobebuiltuponazonethatallowstheframeworktointimatelymanageitsexecutioncontext.

Moreplainly,thismeansthatAngularcantellwhenasynchronousthingsarehappeningthatitmightcareabout.Ifthissoundsabitlikehow$scope.apply()wasrelevantinAngular1.x,youarethinkingintherightway.

Page 628: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

NgZoneAngular2'sintegrationwithzonestakestheformoftheNgZoneservice,whichactsasasortofwrapperfortheactualAngularzones.ThisserviceexposesausefulAPIthatyoucantapinto.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/8676/.

Page 629: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyAllthatisneededforthisrecipeisacomponentintowhichtheNgZoneservicecanbeinjected:

[src/app/app.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-root',

template:``

})

exportclassAppComponent{

constructor(){

}

}

Page 630: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...BeginbyinjectingtheNgZoneserviceintoyourcomponent,whichismadeavailableinsidethecoreAngularmodule:

[src/app/app.component.ts]

import{Component,NgZone}from'@angular/core';

@Component({

selector:'app-root',

template:``

})

exportclassAppComponent{

constructor(privatezone:NgZone){

}

}

TheNgZoneserviceexposesanumberofEventEmittersthatyoucanattachto.Sincezonesarecapableoftrackingasynchronousactivitywithinit,theNgZoneserviceexposestwoEventEmittersthatunderstandwhenanasynchronousactivitybecomesenqueuedanddequeuedasmicrotasks.

TheonUnstableEventEmitterletsyouknowwhenoneormoremicrotasksareenqueued;onStableisfiredwhenthethemicrotaskqueueisemptyandAngulardoesnotplantoenqueueanymore.

Addhandlerstoeach:

[src/app/app.component.ts]

import{Component,NgZone}from'@angular/core';

@Component({

selector:'app-root',

template:``

})

exportclassAppComponent{

constructor(privatezone:NgZone){

zone.onStable.subscribe(()=>console.log('stable'));

zone.onUnstable.subscribe(()=>console.log('unstable'));

}

}

Terrific!However,thelogoutputofthisisquiteboring.Atapplicationstartup,you'llseethattheapplicationisreportedasstable,butnothingfurther.

Demonstratingthezonelifecycle

Page 631: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

IfyouunderstandhowAngularuseszones,thelackofloggingshouldn'tsurpriseyou.There'snothingtogenerateasynchronoustasksinthiszone.Goaheadandaddsomebycreatingabuttonwithahandlerthatsetsatimeoutlogstatement:

[src/app/app.component.ts]

import{Component,NgZone}from'@angular/core';

@Component({

selector:'app-root',

template:`<button(click)="foo()">foo</button>`

})

exportclassAppComponent{

constructor(privatezone:NgZone){

zone.onStable.subscribe(()=>console.log('stable'));

zone.onUnstable.subscribe(()=>console.log('unstable'));

}

foo(){

setTimeout(()=>console.log('timeouthandler'),1000);

}

}

Nowwitheachclick,youshouldseeanunstable-stablepair,followedbyanunstable-timeouthandler-stablesetonesecondlater.Thismeansyou'vesuccessfullytiedintoAngular'szoneemitters.

Page 632: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Inordertoobviatethenecessityofa$scope.apply()construct,Angularneedstheabilitytointelligentlydecidewhenitshouldchecktoseewhetherthestateoftheapplicationhaschanged.

Inanasynchronoussetting,suchasabrowserenvironment,thisseemslikeamessytaskatfirst.Somethingliketimedeventsandinputeventsare,bytheirverynature,difficulttokeeptrackof.Forexample,refertothefollowingcode:

element.addEventListener('click',_=>{

//dosomestuff

setInterval(_=>{

//dosomestuff

},1000);

});

Thiscodeiscapableofchangingthemodelintwodifferentplaces,bothofwhichareasynchronous.Codesuchasthisiswrittenallthetimeandinsomanydifferentplaces;it'sdifficulttoimagineawayofkeepingtrackofsuchcodewithoutsprinklingsomethinglike$scope.$apply()allovertheplace.

Theutilityofzone.js

Thebigideaofzonesistogiveyoutheabilitytograsphowandwhenthebrowserisperformingasynchronousactionsthatyoucareabout.

NgZoneiswrappingtheunderlyingzoneAPIforyouinsteadofexposingEventEmitterstothevariouspartsofthelifecycle,butthisshouldn'tconfuseyouonebit.Forthisexample,thelogoutputisdemonstratingthefollowing:

1. Theapplicationinitializesandexaminestheapplicationzone.Therearenotasksscheduled,soAngularemitsastableevent.

2. Youclickonthebutton.3. Thisgeneratesaclickevent,whichinturngeneratesataskinsidethezonetoexecutethe

clickhandler.Angularseesthisandemitsanunstableevent.4. Theclickhandlerisexecuted,schedulingataskin1second.5. Theclickhandleriscompleted,andtheapplicationonceagainhasnopendingtasks.Angular

emitsastableevent.6. OnesecondelapsesandthebrowsertimeraddsthesetTimeouthandlertothetaskqueue.

Sincethisisshimmedbythezone,Angularseesthisoccurandemitsanunstableevent.7. ThesetTimeouthandlerisexecuted.8. ThesetTimeouthandleriscompleted,andtheapplicationonceagainhasnopendingtasks.

Angularemitsastableevent.

Page 633: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoWorkingwithzonesoutsideAngularisanexcellentintroductiontohowzonesworkinthebrowserExecutionoutsidetheAngularzoneshowsyouhowtoperformlong-runningoperationswithoutincurringazoneoverheadConfiguringcomponentstouseexplicitchangedetectionwithOnPushdescribeshowtomanuallycontrolAngular'schangedetectionprocess

Page 634: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ExecutionoutsidetheAngularzoneTheutilityofzone.jsisterrific,sinceitworksautomatically,butaseasonedsoftwareengineerknowsthisoftencomesataprice.Thisisespeciallytruewhentheconceptofdatabindingcomesintoplay.

Inthisrecipe,you'lllearnhowtoexecuteoutsidetheAngularzoneandwhatbenefitsthisaffordsyou.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/3362/.

Page 635: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Tocompareexecutionindifferentcontexts,createtwobuttonsthatrunthesamecodeindifferentzonecontexts.Thebuttonsshouldcountto100withsetTimeoutincrements.Usetheperformanceglobaltomeasurethetimeittakes:

[src/app/app.component.ts]

import{Component,NgZone}from'@angular/core';

@Component({

selector:'app-root',

template:`

<button(click)="runInsideAngularZone()">

RuninsideAngularzone

</button>

<button(click)="runOutsideAngularZone()">

RunoutsideAngularzone

</button>

`

})

exportclassAppComponent{

progress:number=0;

startTime:number=0;

constructor(privatezone:NgZone){}

runInsideAngularZone(){

this.start();

this.step(()=>this.finish('InsideAngularzone'));

}

runOutsideAngularZone(){

this.start();

this.step(()=>this.finish('OutsideAngularzone'));

}

start(){

this.progress=0;

this.startTime=performance.now();

}

finish(location:string){

this.zone.run(()=>{

console.log(location);

console.log('Took'+

(performance.now()-this.startTime)+'ms');

});

}

step(doneCallback:()=>void){

if(++this.progress<100){

Page 636: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

setTimeout(()=>{

this.step(doneCallback);

},10);

}else{

doneCallback();

}

}

}

Atthispoint,thetwobuttonswillbehaveidentically,asbothofthemarebeingexecutedinsidetheAngularzone.

InordertoexecuteoutsidetheAngularzone,you'llneedtousetherunOutsideAngular()methodexposedbyNgZone:

runInsideAngularZone(){

this.start();

this.step(()=>this.finish('InsideAngularzone'));

}

runOutsideAngularZone(){

this.start();

this.zone.runOutsideAngular(()=>{

this.step(()=>this.finish('OutsideAngularzone'));

});

}

Atthispoint,youcanrunbothofthemagainsidebysideandverifythattheystilltake(roughly)thesameamountoftimetoexecute.Thisshouldnotsurpriseyou,astheyarestillperformingthesametask.Theinclusionofzone.jsmeansthatthebrowserAPIsareshimmedoutsideAngular,soevenrunningthisoutsidetheAngularzonemeansitisstillrunninginsideazone.

Inordertoseeaperformancedifference,you'llneedtointroducesomebindinginsidethetemplate:

[src/app/app.component.ts]

import{Component,NgZone}from'@angular/core';

@Component({

selector:'app-root',

template:`

<h3>Progress:{{progress}}%</h3>

<button(click)="runInsideAngularZone()">

RuninsideAngularzone

</button>

<button(click)="runOutsideAngularZone()">

RunoutsideAngularzone

</button>

`

})

Page 637: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassAppComponent{

...

}

Nowyoushouldbegintoseeasubstantivedifferencebetweentheruntimesofeachbutton.Thisshowsthatwhentheoverheadofbindingsiscausingtheapplicationtoslowdown,runOutsideAngular()hasthepotentialtoyieldsurprisinglysubstantiveperformanceoptimizations.

Page 638: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...WhenyouexaminetheNgZonesource,you'llfindthatthe"outer"zoneismerelythetopmostbrowserzone.AngularforksthiszoneuponinitializationandbuildsuponittoyieldtheniceNgZoneservicewrapper.

However,becausezonesdonotdiscriminateintherealmofasynchronouscallbacksanddatabinding,eachinvocationofthesetTimeouthandlerinsidetheAngularzoneisrecognizedasaneventthathasimplicationsonthetemplaterenderingprocess.Ineveryinvocation,Angularseesanupdatetothebounddatafollowinganasynchronoustask,andproceedstorerendertheview.Whenthisisdone100times,itaddsuptoseveralhundredextramillisecondsofexecution.

WhenthisisrunoutsidetheAngularzone,AngularisnolongerawareofthesetTimeouttasksthatarebeingexecutedandsodoesnotrequirearerender.Upontheveryfinaliterationthough,invokeNgZone.run();thiswillcausetheexecutiontorejointheAngularzone.Angularseesthetaskandthemodifieddataandupdatesthebindingsaccordingly;thistimethough,thisisdoneonlyonce.

Page 639: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Inthisrecipe,thefinish()methodinvokestherun()methodforboththeAngularzoneandthenon-Angularzone.Whenthezonethatthisisinvokeduponisalreadythecontextualzoneinwhichthetaskisbeingexecuted,usingrun()becomesredundantandiseffectivelyano-op.

Page 640: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoWorkingwithzonesoutsideAngularisanexcellentintroductiontohowzonesworkinthebrowserListeningforNgZoneeventsgivesabasicunderstandingofhowAngularisusingzonesConfiguringcomponentstouseexplicitchangedetectionwithOnPushdescribeshowtomanuallycontrolAngular'schangedetectionprocess

Page 641: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConfiguringcomponentstouseexplicitchangedetectionwithOnPushTheconventionofAngular'sdataflow,inwhichdataflowsdownwardthroughthecomponenttreeandeventsfloatupwards,engenderssomeinterestingpossibilities.OneoftheseinvolvescontrollingwhenAngularshouldperformchangedetectionatagivennodeinthecomponenttree.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/4909/.

Page 642: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwiththefollowingsimpleapplication:

[app/root.component.ts]

import{Component}from'@angular/core';

import{Subject}from'rxjs/Subject';

import{Observable}from'rxjs/Observable';

@Component({

selector:'root',

template:`

<button(click)="shareSubject.next($event)">Share!</button>

<article[shares]="shareEmitter"></article>

`

})

exportclassRootComponent{

shareSubject:Subject<Event>=newSubject();

shareEmitter:Observable<Event>=

this.shareSubject.asObservable();

}

[app/article.component.ts]

import{Component,Input,ngOnInit}from'@angular/core';

import{Observable}from'rxjs/Observable';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{count}}</p>

`

})

exportclassArticleComponentimplementsOnInit{

@Input()shares:Observable<Event>;

count:number=0;

title:string=

'InsuranceFraudGrowsInWakeofApplePieHubbub';

ngOnInit(){

this.shares.subscribe((e:Event)=>++this.count);

}

}

Thisverysimpleapplicationisjustusinganobservabletopassshareeventsdown,fromaparentcomponenttoachildcomponent.Thechildcomponentkeepstrackoftheclickeventcountandinterpolatesthiscounttothepage.

Yourobjectiveistomodifythissetupsothatthechildcomponentonlydetectsachangewhenaneventisemittedfromtheobservable.

Page 643: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...Outofthebox,Angularalreadyhasaveryrobustwayofdetectingchange:zones.Wheneverthereisaneventinsideazone,Angularrecognizesthatthiseventhasthepotentialtomodifytheapplicationinameaningfulway.Itthenperformschangedetectionfromthetopofthecomponenttreedowntothebottom,checkingwhetheranythingneedstobeupdatedinthechangedmodel.Sincedataonlyflowsdownward,thisisalreadyanextremelyefficientwayofhandlingit.

However,youmightliketoexertsomecontrolinthissituationsinceyoumaybeabletohand-optimizewhenchangedetectionshouldoccurinsideacomponent.Mostlikelyyouwillveryeasilybeabletotellwhenacomponentmayormaynotchange,basedonwhatishappeningaroundit.

ConfiguringtheChangeDetectionStrategy

Angularoffersyoutheoptionofchanginghowthechangedetectionschemeworks.IfyouwouldpreferthatAngularrefrainfromlisteningtozoneeventstokickoffaroundofchangedetection,youcaninsteadconfigureacomponenttoonlyperformchangedetectionwhenaninputischanged.ThiscanbedonebyconfiguringthecomponenttousetheOnPushstrategyinsteadofthedefault.

Surely,thiswilloccurlessoftenthanthefirehoseofbrowserevents.Ifthecomponentwillonlychangewhenaninputischanged,thenthiswillsaveAngularthetroubleofdoinganiterationofchangedetectiononthatcomponent.

ModifyArticleComponenttoinsteaduseOnPush:

[app/article.component.ts]

import{Component,Input,ngOnInit,ChangeDetectionStrategy}

from'@angular/core';

import{Observable}from'rxjs/Observable';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{count}}</p>

`,

changeDetection:ChangeDetectionStrategy.OnPush

})

exportclassArticleComponentimplementsOnInit{

@Input()shares:Observable<Event>;

count:number=0;

title:string=

'InsuranceFraudGrowsInWakeofApplePieHubbub';

Page 644: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ngOnInit(){

this.shares.subscribe((e:Event)=>++this.count);

}

}

Thissuccessfullychangesthestrategy,butthereisonesignificantproblemnow:thecountwillnolongerbeupdated.Thishappens,ofcourse,becausetheinputtothiscomponentisnotbeingchanged.

ThecountonlyupdatedbeforebecauseAngularwasseeingclickeventsonthebutton,whichcausedchangedetectiononArticleComponent.Now,Angularhasbeeninstructedtoignoretheseclickevents,eventhoughthecountinsidethechildcomponentisstillbeingupdatedfromtheobservablehandlers.

Requestingexplicitchangedetection

Youareabletoinjectareferencetothecomponent'schangedetector.Withthis,itexposesamethodthatallowsyoutoforcearoundofchangedetectionwheneveryoulike.Sincethemodelisbeingupdatedinsideanobservablehandler,thisseemslikeafineplacetotriggerthechangedetectionprocess:

[app/article.component.ts]

import{Component,Input,ngOnInit,ChangeDetectionStrategy,

ChangeDetectorRef}from'@angular/core';

import{Observable}from'rxjs/Observable';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

<p>Shares:{{count}}</p>

`,

changeDetection:ChangeDetectionStrategy.OnPush

})

exportclassArticleComponentimplementsOnInit{

@Input()shares:Observable<Event>;

count:number=0;

title:string=

'InsuranceFraudGrowsInWakeofApplePieHubbub';

constructor(privatechangeDetectorRef_:ChangeDetectorRef){}

ngOnInit(){

this.shares.subscribe((e:Event)=>{

++this.count;

this.changeDetectorRef_.markForCheck();

});

}

}

Page 645: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Nowyouwillseethecountgettingupdatedonceagain.

Page 646: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...UsingOnPushessentiallyconvertsacomponenttooperatelikeablackbox.Iftheinputdoesn'tchange,thenAngularassumesthatthestateinsidethecomponentwillremainconstant,andthereforethereisnoneedtoproceedfurtherwithregardtodetectingachange.

Sincechangedetectionalwaysflowsfromtoptobottominthecomponenttree,therequestforchangedetectionusesmarkForCheck(),marksthecomponent'schangedetectortorunonlywhenAngularreachesthatcomponentinsidethetree.

Page 647: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Thisisausefulpatternifyou'relookingtosqueezeadditionalperformancefromyourapplication,butinsomecases,doingthiscandevelopintoananti-pattern.TheneedtoexplicitlydefinewhenAngularshouldperformchangedetectioncanbecometediousasyourcomponentcodegrowsinsize.TherecanpotentiallybebugsthatarisefrommissingoneplacewheremarkForCheck()shouldhavebeeninvokedbutwasnot.Angular'schangedetectionstrategyisalreadyquiteperformantandrobust,sousethisconfigurationwisely.

Page 648: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoWorkingwithzonesoutsideAngularisanexcellentintroductiontohowzonesworkinthebrowserListeningforNgZoneeventsgivesabasicunderstandingofhowAngularisusingzonesExecutionoutsidetheAngularzoneshowsyouhowtoperformlong-runningoperationswithoutincurringazoneoverheadConfiguringViewEncapsulationformaximumefficiencyshowshowyoucanconfigurecomponentstoutilizetheshadowDOM

Page 649: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConfiguringViewEncapsulationformaximumefficiencyAlthoughitmaysoundclichéd,Angular2wasbuiltforthebrowsersoftomorrow.Youcanpointtowhythisisthecaseinalargenumberofways,butthereisonewaywherethisisextremelytrue:componentencapsulation.

TheidealcomponentmodelforAngular2istheoneinwhichcomponentsareentirelysandboxed,saveforthefewpiecesthatareexternallyvisibleandmodifiable.Inthisrespect,itdoesabang-upjob,buteventhemostmodernbrowserslimititsabilitytostriveforsuchefficacy.ThisisespeciallytrueintherealmofCSSstyling.

SeveralfeaturesofAngular'scomponentstylingareespeciallyimportant:

YouareabletowritestylesthatareguaranteedtobeonlyapplicabletoacomponentYoucanexplicitlyspecifystylesthatshouldbeinheriteddownwardthroughthecomponenttreeYoucanspecifyanencapsulationstrategyonapiecewisebasis

Thereareanumberofinterestingwaystoaccomplishanefficientcomponentstylingschema.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/1463/.

Page 650: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwithasimpleapplicationcomponent:

[src/app/app.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-root',

template:`

<h1>

{{title}}

</h1>

`,

styles:[`

h1{

color:red;

}

`]

})

exportclassAppComponent{

title='appworks!';

}

Page 651: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...AngularwilldefaulttonotusingShadowDOMforcomponents,asthemajorityofbrowsersdonotsupportittoasatisfactorylevel.Thenextbestthing,whichitwilldefaultto,istoemulatethisstylingencapsulationbynestingyourselectors.Theprecedingcomponentiseffectivelytheequivalentofthefollowing:

[src/app/app.component.ts]

import{Component,ViewEncapsulation}from'@angular/core';

@Component({

selector:'app-root',

template:`

<h1>

{{title}}

</h1>

`,

styles:[`

h1{

color:red;

}

`],

encapsulation:ViewEncapsulation.Emulated

})

exportclassAppComponent{

title='appworks!';

}

Emulatedstylingencapsulation

HowexactlydoesAngularperformthisemulation?Lookattherenderedapplicationtofindout.Yourcomponentwillappearsomethinglikethefollowing:

<app-root_nghost-mqf-1="">

<h1_ngcontent-mqf-1="">

appworks!

</h1>

</app-root>

Intheheadofthedocument:

<style>

h1[_ngcontent-mqf-1]{

color:red;

}

</style>

Thepictureshouldbeabitclearernow.Angularisassigningthiscomponentclass(notaninstance)auniqueID,andthestylesdefinedfortheglobaldocumentwillonlybeapplicableto

Page 652: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

tagsthathavethematchingattribute.

Nostylingencapsulation

Ifthisencapsulationisn'tnecessary,youarefreetouseencapsulation:ViewEncapsulation.None.AngularwillhappilyskiptheuniqueIDassignmentstepforyou,givingyouavanilla:

<app-root>

<h1>

appworks!

</h1>

</app-root>

Intheheadofthedocument:

<style>

h1{

color:red;

}

</style>

Nativestylingencapsulation

Thebest,mostfuturistic,andleastsupportedmethodofgoingaboutthisistouseShadowDOMinstancestogoalongwitheachcomponentinstance.Thiscanbeaccomplishedusingencapsulation:ViewEncapsulation.Native.Now,yourcomponentwillrender:

<app-root>

#shadow-root

<style>

h1{

color:red;

}

</style>

<h1>

appworks!

</h1>

</app-root>

Page 653: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Angularissmartenoughtorecognizewhereitneedstoputyourstylesandhowtomodifythemtomakethemworkforyourcomponentconfiguration:

ForNoneandEmulated,stylesgointothedocumentheadForNative,stylesgoinlinewiththerenderedcomponentForNoneandNative,nostylemodificationsareneededForEmulated,stylesarerestrictedbyattributeselectors

Page 654: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...AnimportantconsiderationofViewEncapsulationchoicesisCSSperformance.ItiswellknownandentirelyintuitivethatCSSstylingismoreefficientwhenithastotraverseasmallerpartoftheDOMandhastomatchusingasimplerselector.

Emulatingcomponentencapsulationaddsanattributeselectortoeachandeverystylethatisdefinedforthatcomponent.Atscale,itisn'thardtoseehowthiscandegradeperformance.ShadowDOMelegantlysolvesthisproblembyofferingunmodifiedstylesinsidearestrictedpieceofDOM.Itsstylescannotescapebutcanbeapplieddownwardtoothercomponents.Furthermore,ShadowDOMcomponentscanbenestedandstrategicallyapplied.

Page 655: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnderstandingandproperlyutilizingenableProdModewithpureandimpurepipesdescribeshowtotakethetrainingwheelsoffyourapplicationConfiguringcomponentstouseexplicitchangedetectionwithOnPushdescribeshowtomanuallycontrolAngular'schangedetectionprocess

Page 656: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConfiguringtheAngular2RenderertousewebworkersOneofthemostcompellingintroductionsinthenewrenditionoftheAngularframeworkisthetotalabstractionoftherenderingexecution.ThisstemsfromoneofthecoreideasofAngular:youshouldbeabletoseamlesslysubstituteoutanybehaviormoduleandreplaceitwithanother.This,ofcourse,meansthatAngularcannothaveanydependencybleedoutsideofthemodules.

OneplacethatAngularputsemphasisonbeingconfigurableisthelocationwherecodeexecutiontakesplace.Thisismanifestedinanumberofways,andthisrecipewillfocusonAngular'sabilitytoperformrenderingexecutionatalocationotherthaninsidethemainbrowser'sJavaScriptruntime.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/1859/.

Page 657: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyBeginwithsomesimpleapplicationelementsthatdonotyetformafullapplication:

[index.html]

<!DOCTYPEhtml>

<html>

<head>

<scriptsrc="zone.js"></script>

<scriptsrc="reflect-metadata.js"></script>

<scriptsrc="system.src.js"></script>

<scriptsrc="system-config.js"></script>

</head>

<body>

<article></article>

<script>

System.import('system-config.js')

.then(function(){

System.import('main.ts');

});

</script>

</body>

</html>

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h2>{{title}}</h2>

`

})

exportclassArticleComponent{

title:string=

'SurveyIndicatesPlasticFunnelBestWaytoDrinkRareWine';

}

NotethatthisrecipeusesSystemJStohandlemodulesandTypeScripttranspilation,butthisismerelytokeepthedemonstrationsimple.AproperlycompiledapplicationcanuseregularJavaScripttoaccomplishthesamefeat,withnodependencyonSystemJS.

Thisrecipewillstartoffbyassumingyouhaveabasicknowledgeofwhatwebworkersareandhowtheywork.Thereisadiscussionoftheirpropertieslaterinthisrecipe.

Page 658: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...TheSystemJSstartupconfigurationkicksofftheapplicationfrommain.ts,soyou'llbeginthere.Insteadofbootstrappingtheapplicationinthisfileasyounormallywould,you'lluseanimportedAngularhelpertoinitializethewebworkerinstance:

[main.ts]

import{bootstrapWorkerUi}from"@angular/platform-webworker";

bootstrapWorkerUi(window.location.href+"loader.js");

Concernyourselfwiththeprecisedetailsofwhatthisisdoinglater,butthehigh-levelideaisthatthisiscreatingawebworkerinstancethatisintegratedwiththeAngularRenderer.

Note

RecallthatinitializingawebworkerrequiresapathtoitsstartupJSfile,andarelativepathinsidethisfilemightnotalwayswork;therefore,you'reusingthewindowlocationtoprovideanabsoluteURL.Thenecessityofthismaydifferbasedonyourdevelopmentsetup.

Sincethisfilereferencesawebworkerinitializationfilecalledloader.js,writeitnext.WorkerscannotbegivenaTypeScriptfile:

[loader.js]

importScripts(

"system.js",

"zone.js",

"reflect-metadata.js",

"./system-config.js");

System.import("./web-worker-main.ts");

Thisfilefirstimportsthesamefilesthatarelistedinsidethe<head>tagofindex.html.ThisshouldmakesensesincethewebworkerwillneedtoperformsomeofthedutiesofAngularbutwithoutdirectaccesstoanythingthatexistsinsidethemainJavaScriptruntime.Forexample,sincethiswebworkerwillberenderingacomponent,itneedstobeabletounderstandthe@Component({})notation,whichcannotbedonewithoutthereflect-metadataextension.

Justlikethemainapplication,thewebworkeralsohasaninitializationfile.Thistakestheformofweb-worker-main.ts:

[web-worker-main.ts]

import{AppModule}from'./app/app.module';

import{platformWorkerAppDynamic}

from'@angular/platform-webworker-dynamic';

Page 659: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

platformWorkerAppDynamic().bootstrapModule(AppModule);

Comparedtoanormalmain.tsfile,thisfileshouldlookdelightfullyfamiliar.Angularprovidesyouwithatotallyseparateplatformmodule,butonethataffordsyouanidenticalAPI,whichyouareusingheretobootstraptheapplicationfromtheyet-to-be-definedAppModule.Definethisnext:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{WorkerAppModule}from'@angular/platform-webworker';

import{AppModule}from'./app.module';

import{ArticleComponent}from'./article.component';

@NgModule({

imports:[

WorkerAppModule

],

bootstrap:[

ArticleComponent

],

declarations:[

ArticleComponent

]

})

exportclassAppModule{}

SimilartohowyouwouldnormallyuseBrowserModulewithinaconventionaltop-levelappmodule,Angularprovidesaweb-worker-flavoredWorkerAppModulethathandlesallthenecessaryintegration.

Page 660: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Makenomistakeaboutwhatishappeninghere:yourapplicationisnowrunningontwoseparateJavaScriptthreads.

WebworkersarebasicallyjustreallydumbJavaScriptexecutionbuckets.Wheninitialized,theyaregivenaninitialpieceofJavaScripttorun,whichinthisexampletooktheformofloader.js/.Theyhavenounderstandingofwhatisgoingoninthemainbrowserruntime.Theycan'tinteractwiththeDOM,andyouareonlyabletocommunicatewiththemviaPostMessages.AngularbuildsanelegantabstractionontopofPostMessagestocreateabusinterface,anditisthisinterfacethatisusedtojointhetworuntimestogether.

IfyoulookintothePostMessagespecification,youwillnoticethatallofthedatapassedasthemessagemustbeserializedintoastring.HowthencanthisrenderingconfigurationpossiblyworkwiththeDOMinthemainbrowser,handlingeventsanddisplayingtheHTMLandthewebworkerperformingtherenderingonaDOMitcannottouch?

Theanswerissimple:Angularserializeseverything.Whentheinitialrenderingoccurs,oraneventinthebrowseroccurs,Angulargrabseverythingitneedstoknowaboutthecurrentstate,wrapsitupintoaserializedstring,andshipsitofftothewebworkerrendererontheproperchannel.Thewebworkerrendererunderstandswhat'sbeingpassedtoit.AlthoughitcannotaccessthemainDOM,itiscertainlyabletoconstructHTMLelements,understandhowtheserializedeventspassedtoitwillaffectthem,andperformtherendering.

Whenthetimecomestotellthebrowserwhattoactuallyrender,itwillinturnserializetherenderedcomponentandsenditbacktothemainbrowserruntime,whichwillunpackthestringandinsertitintotheDOM.

TotheAngularframework,becauseitisabstractedfromallthebrowserdependenciesthatmightgetinthewayofthis,everythingseemsnormal.Eventscomein,they'rehandled,andarendererservicetellsitwhattoputintotheDOM.EverythingthathappensinbetweenisunimportanttotheAngularframework,whichdoesn'tcarethateverythinghappenedinatotallyseparateJavaScriptruntime.

Note

Notethattheelementsyoubeginwithhavenothingunusualaboutthemtoallowwebworkercompatibility.ThisunderscorestheeleganceofAngular'swebworkerabstraction.

Page 661: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...Aswebworkersaremorefullysupportedandutilized,patternssuchasthesewillmostlikelybecomemoreandmorecommon,anditisextremelyprescientoftheAngularteamtosupportthisbehavior.Thereare,however,someconsiderations.

Optimizingforperformancegains

Oneoftheprimarybenefitsofusingwebworkersisthatyounowhaveaccesstoanexecutioncontextthatisnotblockedbyanythingrunningonthebrowserexecutioncontext.Forperformancedrags,suchasreflowandblockingofeventloophandlers,thewebworkerwillcontinuewiththeexecutionwithoutacareforwhatishappeningelsewhere.

Therefore,gettingaperformancebenefitbecomesaproblemofoptimization.CommunicationbetweenthetwoJavaScriptthreadsrequiresserializationandtransmissionofevents,whichisobviouslynotasfastashandlingtheminthesamethread.However,inanespeciallycomplicatedapplication,renderingcanquicklybecomeoneofthemostexpensivethingsyourapplicationwilldo.Therefore,youmayneedtoexperimentandmakeajudgmentcallforyourapplication,asnotallapplicationswillseeperformancegainsfromusingwebworkers—onlythosewhererenderingbecomesprohibitivelyexpensive.

Compatibilityconsiderations

Webworkershaveextremelygoodsupport,butthereisstillaverysignificantnumberofbrowsersthatdonotsupportthem.Ifyourapplicationneedstoworkuniversally,webworkersarenotrecommended;sincetheywillnotgracefullydegrade,yourapplicationwillmerelyfail.

Page 662: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoConfiguringtheAngular2rendererservicetousewebworkersguidesyouthroughthesettingupofyourapplicationtorenderonawebworkerConfiguringapplicationstouseahead-of-timecompilationguidesyouthroughhowtocompileanapplicationduringthebuildConfiguringanapplicationtouselazyloadingshowshowyoucandelayservingchunksofapplicationcodeuntilneeded

Page 663: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Configuringapplicationstouseahead-of-timecompilationAngular2introducestheconceptofahead-of-timecompilation(AOT).Thisisanalternateconfigurationinwhichyoucanrunyourapplicationstomovesomeprocessingtimefrominsidethebrowser(referredtoasjust-in-timecompilationorJIT)towhenyoucompileyourapplicationontheserver.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/9253/.

Page 664: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadyAOTcompilationisapplication-agnostic,soyoushouldbeabletoaddthistoanyexistingAngular2applicationwithminimalmodification.

Forthepurposesofthisexample,supposeyouhaveanexistingAppModuleinsideapp/app.module.ts.Youneedn'tconcernyourselfwithitscontentsinceitisirrelevantforthepurposeofAOT.

Page 665: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...UsingAOTmeansyouwillcompileandbootstrapyourapplicationdifferently.Dependingonhowitisconfigured,youwillprobablywanttousesiblingfileswith"aot"addedtothename.Bearinmindthatthisisonlyfororganizationalpurposes;Angularisnotconcernedwithwhatyounameyourfiles.

InstallingAOTdependencies

Angularrequiresanewcompilationtoolngc,whichisincludedinthe@angular/compiler-clipackage.ItwillalsomakeuseofplatformBrowsertobootstrap,whichisprovidedinsidethe@angular/platform-browserpackage.Installbothoftheseandsavethedependencytopackage.json:

npminstall@angular/compiler-cli@angular/platform-server--save

Configuringngc

ngcwillessentiallyperformasupersetofdutiesofthetsccompilationtoolyouareaccustomedtousing.Itiswisetoprovideitwithaseparateconfigfile,whichcanbenamedtsconfig-aot.json(butyouarenotbeholdentothisname).

Thefileshouldappearidenticaltoyourexistingtsconfig.jsonfilebutwiththefollowingimportantmodifications:

[tsconfig-aot.json]

{

"compilerOptions":{

(lotsofsettingsherealready,onlychange'module')

"module":"es2015",

},

"files":[

"app/app.module.ts",

"main.ts"

],

"angularCompilerOptions":{

"genDir":"aot",

"skipMetadataEmit":true

}

}

AligningcomponentdefinitionswithAOTrequirements

AOTcompilationrequiresyourapplicationtobeorganizedinafewspecificways.Nothingshouldbreakanexistingapplication,butthey'reimportanttodoandtakenoteof.Ifyou'vebeen

Page 666: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

followingAngularbestpractices,theyshouldbeabreeze.

First,componentdefinitionsmusthaveamoduleIdspecified.YouwillfindthatcomponentsgeneratedwiththeAngularCLIalreadyhavethisincluded.Ifnot,themoduleIdshouldalwaysbemodule.id,asshownhere:

@Component({

moduleId:module.id,

(lotsofotherstuff)

})

SincethemoduleisundefinedwhencompilinginAOT,youcanprovideadummyvalueintheroottemplate:

<script>window.module='aot';</script>

Tip

AOTdoesn'tneedthemoduleID,butitallowsyoutohaveacompilationwithouterrors.NotethatthesestepsinvolvingmoduleIdmaybechangedoreliminatedentirelyinfutureversionsofAngular,astheyonlyexisttoallowyoutohavecompatibilitybetweenbothJITandAOTcompilations.

Next,you'llneedtochangethepathofthetemplates,bothCSSandHTML,toberelativetothecomponentdefinitionfile,notapplication-root-relative.IfyouareusingtheconventiongivenbytheAngularCLIorthestyleguide,youareprobablyalreadydoingthis.

Compilingwithngc

ngcisn'tavailabledirectlyonthecommandline;you'llneedtorunthebinaryfiledirectly.Additionally,sinceinthisexampleit'snotusingthetsconfig.jsonnamingconvention,you'llneedtopointthebinarytothelocationofthealternateconfigfile.Runthefollowingcommandfromtherootofyourapplicationtoexecutethecompilation:

node_modules/.bin/ngc-ptsconfig-aot.json

ThiscommandwilloutputacollectionofNgFactoryfileswith.ngfactory.tsinsidetheaotdirectory,asyouspecifiedearlierintheangularCompilerOptionssectionoftsconfig-aot.json.

Thecompilationisdone,butyourapplicationdoesn'tyetknowhowtousetheseNgFactoryinstances.

BootstrappingwithAOT

YourapplicationwillnowstartoffwithAppModuleNgFactoryinsteadofAppModule.Bootstrap

Page 667: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

itusingplatformBrowser:

[main.ts]

import{platformBrowser}from'@angular/platform-browser';

import{AppModuleNgFactory}from'aot/app/app.module.ngfactory';

platformBrowser().bootstrapModuleFactory(AppModuleNgFactory);

Withthis,you'llbeabletorunyourapplicationnormallybutthistimeusingprecompiledfiles.

Page 668: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...CompilinginAngularisacomplexsubject,butthereareseveralmainpointsrelevanttoswitchinganapplicationtoanAOTbuildprocess:

TheAngularcompilerlivesinsidethe@angular/compilermodule,whichisquitelarge.UsingAOTmeansthismodulenolongerneedstobedeliveredtothebrowser,whichyieldssubstantialbandwidthsavings.TheAngularcompilationprocessinvolvestakingthetemplatestringsinsideyourcomponents,whichexistasdefinedbytemplateortemplateUrl,andconvertingthemintoNgFactoryinstances.TheseinstancesspecifyhowAngularunderstandshowyoudefinedthetemplate.TheconversiontoFactoryinstanceshappensbothinJITandAOTcompilation;switchingtoAOTsimplymeansyouaredoingthisprocessingontheserverinsteadoftheclientandservingNgFactorydefinitionsdirectlyinsteadofuncompiledtemplatestrings.AOTwillobviouslyslowdownyourbuildtimesincemorecomputationisrequiredeachtimethecodebaseneedsupdating.SinceyoucantrustthatAngularwillbeabletocorrectlyinterpretNgFactorydefinitionsthattheAOTcompilationgenerates,itisbesttodoJITcompilationwhendevelopingtheapplicationandAOTcompilationwhenbuildinganddeployingproductionapplications.

Page 669: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...AOTiscool,butitmightnotbeforeverybody.ItwillverylikelyreducetheloadtimeandinitializationtimeofyourAngularapplication,butyourbuildprocessisconsiderablymoreinvolvednow.What'smore,ifyouwanttouseJITindevelopmentbutAOTinproduction,younowhavetomaintaintwoversionsofthreedifferentfiles:index.html,main.ts,andtsconfig.json.Perhapsthisadditionalbuildcomplexityoverheadisworthit,butitshouldcertainlybeajudgmentcallbasedonyourdevelopmentsituation.

GoingfurtherwithTreeShaking

AngularalsosupportsaseparatestepofoptimizationcalledTreeShaking.Thisusesaseparatenpmlibrarycalledrollup.Essentially,thislibraryreadsinyourentireapplication(asJSfiles),figuresoutwhatmodulesarenotbeingused,andcutsthemoutofthecompiledcodebaseandthereforeofthepayloadthatisdeliveredtothebrowser.Thisisanalogoustoshakingatreetomakethedeadbranchesfallout,hence"treeshaking."

Note

Thees2015moduleconfigurationspecifiedearlierintsconfig-aot.jsonwasforsupportingtherolluplibrary,asitisarequirementforcompatibility.

Ifyourapplicationcodebaseiswell-maintained,youwillmostlikelyseelimitedbenefitsoftreeshakingsinceunusedimportsandthelikewillbecaughtwhencoding.What'smore,onecouldmaketheargumentthattreeshakingmightbeananti-patternsinceitsubtlyencouragesaliberaluseofmoduleinclusionbythedeveloperwiththeknowledgethattreeshakingwilldothedirtyworkingofcuttingoutanyunusedmodules.Thismaythenleadtoaclutteredcodebase.

UsingtreeshakingcanbeausefultoolwhenitcomestoAngular2applications,butitsusefulnessisinmanywaysevaporatedbykeepingacodebasetidy.

Page 670: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoUnderstandingandproperlyutilizingenableProdModewithpureandimpurepipesdescribeshowtotakethetrainingwheelsoffyourapplicationConfiguringtheAngular2rendererservicetousewebworkersguidesyouthroughtheprocessofsettingupyourapplicationtorenderonawebworkerConfiguringanapplicationtouselazyloadingshowshowyoucandelayservingchunksofapplicationcodeuntilneeded

Page 671: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

ConfiguringanapplicationtouselazyloadingLazyloadedapplicationsarethosethatdefertheretrievalofrelevantresourcesuntiltheyareactuallynecessary.Onceapplicationsbegintoscale,thiscanyieldmeaningfulgainsinperformance,andAngular2supportslazyloadingrightoutofthebox.

Note

Thecode,links,andaliveexamplerelatedtothisrecipeareavailableathttp://ngcookbook.herokuapp.com/0279/.

Page 672: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

GettingreadySupposeyoubeginwiththefollowingsimpleapplication:

[app/root.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{}

[app/link.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'app-link',

template:`

<arouterLink="/article">article</a>

`

})

exportclassLinkComponent{}

[app/article.component.ts]

import{Component}from'@angular/core';

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

`

})

exportclassArticleComponent{

title:string=

'Baboon'sStockPicksCrushTop-PerformingHedgeFund';

}

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{ArticleComponent}from'./article.component';

import{LinkComponent}from'./link.component';

constappRoutes:Routes=[

{

Page 673: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

path:'article',

component:ArticleComponent

},

{

path:'**',

component:LinkComponent

}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

ArticleComponent,

LinkComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Thisapplicationhasonlytworoutes:thedefaultroute,whichdisplaysalinktothearticlepage,andthearticleroute,whichdisplayArticleComponent.Yourobjectiveistodefertheloadingoftheresourcesrequiredbythearticlerouteuntilitisactuallyvisited.

Page 674: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howtodoit...LazyloadingmeanstheinitialapplicationmodulethatisloadedcannothaveanydependenciesonthemodulethatyouwishtolazilyloadsincenoneofthatcodewillbepresentbeforethenewURLisvisited.First,movetheArticleComponentreferencetoitsownmodule:

[app/article.module.ts]

import{NgModule}from'@angular/core';

import{ArticleComponent}from'./article.component';

@NgModule({

declarations:[

ArticleComponent

],

exports:[

ArticleComponent

]

})

exportclassArticleModule{}

Removingalldependenciesmeansmovingtherelevantroutedefinitionstothismoduleaswell:

[app/article.module.ts]

import{NgModule}from'@angular/core';

import{ArticleComponent}from'./article.component';

import{Routes,RouterModule}from'@angular/router';

constarticleRoutes:Routes=[

{

path:'',

component:ArticleComponent

}

];

@NgModule({

imports:[

RouterModule.forChild(articleRoutes)

],

declarations:[

ArticleComponent

],

exports:[

ArticleComponent

]

})

exportclassArticleModule{}

Next,removeallthesemodulereferencesfromAppModule.Inaddition,modifytheroutedefinition,sothatinsteadofspecifyingacomponenttorender,itsimplyreferencesapathtothelazilyloadedmodule,aswellasthenameofthemoduleusingaspecial#syntax:

Page 675: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{LinkComponent}from'./link.component';

constappRoutes:Routes=[

{

path:'article',

loadChildren:'./app/article.module#ArticleModule'

},

{

path:'**',

component:LinkComponent

}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes)

],

declarations:[

LinkComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

Thisisallthat'srequiredtosetuplazyloading.Yourapplicationshouldbehaveidenticallytowhenyoubeganthisrecipe.

Page 676: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

Howitworks...Toverifythatitisinfactperformingalazyload,starttheapplicationandkeepaneyeontheNetworktabofyourbrowser'sdeveloperconsole.

WhenyouclickonthearticlerouterLink,youshouldseearticle.module.tsandarticle.component.tsrequestsgooutbeforetherenderingoccurs.ThismeansAngularisonlyfetchingtherequiredfilesonceyouactuallyvisittheroute.TheloadChildrenroutepropertytellsAngularthatwhenitvisitsthisroute,ifithasn'tloadedthemodulealready,itshouldusetherelativepathyoumayhaveprovidedtofetchthemodule.Oncethemodulefileisretrieved,Angularisabletoparseitandknowwhichotherfilesitneedstorequesttoloadallthemodule'sdependencies.

Page 677: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

There'smore...You'llnotethatthisintroducesabitofadditionallatencytoyourapplicationsinceAngularwaitstoloadtheresourcesrightwhenitactuallyneedsthem.What'smore,inthisexample,ithastoactuallyperformtwoadditionalroundtripstotheserverwhenitvisitsthearticleURL:onetorequestthemodulefileandonetorequestthemodule'sdependencies.

Inaproductionenvironment,thislatencymightbeunacceptable.Aworkaroundmightbetocompilethelazilyloadedpayloadintoasinglefilethatcanbefetchedwithonerequest.Dependingonhowyourapplicationisbuilt,yourmileagemayvary.

Accountingforsharedmodules

Thelazilyloadedmoduleistotallyseparatedfromyourmainapplicationmodule,andthisincludesinjectables.Ifaserviceisprovidedtothetop-levelapplicationmodule,youwillfindthatitwillcreatetwoseparateinstancesofthatserviceforeachplaceitisprovided—certainlyunexpectedbehavior,giventhatanapplicationloadednormallywillonlycreateoneinstanceifitisonlyprovidedonce.

ThesolutionistopiggybackontheforRootmethodthatAngularusestosimultaneouslyprovideandconfigureservices.Morerelevanttothisrecipe,itallowsyoutotechnicallyprovideaserviceatmultiplelocations;however,Angularwillknowhowtoignoreduplicatesofthis,provideditisdoneinsideforRoot().

First,definetheAuthServicethatyouwishtocreateonlyasingleinstanceof:

[app/auth.service.ts]

import{Injectable}from'@angular/core';

@Injectable()

exportclassAuthService{

constructor(){

console.log('instantiatedAuthService');

}

}

Thisincludesalogstatementsoyoucanseethatonlyoneinstantiationoccurs.

Next,createanNgModulewrapperspeciallyforthisservice:

[app/auth.module.ts]

import{NgModule,ModuleWithProviders}from"@angular/core";

import{AuthService}from"./auth.service";

@NgModule({})

Page 678: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

exportclassAuthModule{

staticforRoot():ModuleWithProviders{

return{

ngModule:AuthModule,

providers:[

AuthService

]

};

}

}

SincethisutilizestheforRoot()strategyasdetailedintheprecedingcode,you'refreetoimportthismodulebothinsidetheapplicationmoduleaswellasthelazilyloadedmodule:

[app/app.module.ts]

import{NgModule}from'@angular/core';

import{BrowserModule}from'@angular/platform-browser';

import{RouterModule,Routes}from'@angular/router';

import{RootComponent}from'./root.component';

import{LinkComponent}from'./link.component';

import{AuthModule}from'./auth.module';

constappRoutes:Routes=[

{

path:'article',

loadChildren:'./app/article.module#ArticleModule'

},

{

path:'**',

component:LinkComponent

}

];

@NgModule({

imports:[

BrowserModule,

RouterModule.forRoot(appRoutes),

AuthModule.forRoot()

],

declarations:[

LinkComponent,

RootComponent

],

bootstrap:[

RootComponent

]

})

exportclassAppModule{}

You'lladdittothelazilyloadedmoduletoo,butdon'tinvokeforRoot().Thismethodisreservedforonlytherootapplicationmodule:

Page 679: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

[app/article.module.ts]

import{NgModule}from'@angular/core';

import{ArticleComponent}from'./article.component';

import{Routes,RouterModule}from'@angular/router';

import{AuthModule}from'./auth.module';

constarticleRoutes:Routes=[

{

path:'',

component:ArticleComponent

}

];

@NgModule({

imports:[

RouterModule.forChild(articleRoutes),

AuthModule

],

declarations:[

ArticleComponent

],

exports:[

ArticleComponent

]

})

exportclassArticleModule{}

Finally,injecttheserviceintoRootComponentandArticleComponentanduselogstatementstoseethatitdoesindeedreachboththecomponents:

[app/root.component.ts]

import{Component}from'@angular/core';

import{AuthService}from'./auth.service';

@Component({

selector:'root',

template:`

<h1>Rootcomponent</h1>

<router-outlet></router-outlet>

`

})

exportclassRootComponent{

constructor(privateauthService_:AuthService){

console.log(authService_);

}

}

[app/article.component.ts]

import{Component}from'@angular/core';

import{AuthService}from'./auth.service';

Page 680: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

@Component({

selector:'article',

template:`

<h1>{{title}}</h1>

`

})

exportclassArticleComponent{

title:string=

'Baboon'sStockPicksCrushTop-PerformingHedgeFund';

constructor(privateauthService_:AuthService){

console.log(authService_);

}

}

Youshouldseeasingleserviceinstantiationandsuccessfulinjectionintoboththecomponents.

Page 681: Angular 2 Cookbook - Programmer BooksDowngrading Angular 2 components to Angular 1 directives with downgradeComponent Getting ready How to do it... How it works... See also Downgrade

SeealsoConfiguringtheAngular2rendererservicetousewebworkersguidesyouthroughtheprocessofsettingupyourapplicationtorenderonawebworkerConfiguringapplicationstouseahead-of-timecompilationguidesyouthroughhowtocompileanapplicationduringthebuild