1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728 |
- /**
- * @license Angular v16.2.9
- * (c) 2010-2022 Google LLC. https://angular.io/
- * License: MIT
- */
- import { getDebugNode, RendererFactory2 as RendererFactory2$1, InjectionToken as InjectionToken$1, ɵstringify, ɵReflectionCapabilities, Directive, Component, Pipe, NgModule, ɵgetInjectableDef, resolveForwardRef as resolveForwardRef$1, ɵNG_COMP_DEF, ɵRender3NgModuleRef, ApplicationInitStatus, LOCALE_ID as LOCALE_ID$1, ɵDEFAULT_LOCALE_ID, ɵsetLocaleId, ɵRender3ComponentFactory, ɵcompileComponent, ɵNG_DIR_DEF, ɵcompileDirective, ɵNG_PIPE_DEF, ɵcompilePipe, ɵNG_MOD_DEF, ɵtransitiveScopesFor, ɵpatchComponentDefWithScope, ɵNG_INJ_DEF, ɵcompileNgModuleDefs, provideZoneChangeDetection, Compiler, COMPILER_OPTIONS, Injector as Injector$1, ɵisEnvironmentProviders, ɵNgModuleFactory, ModuleWithComponentFactories, ɵconvertToBitFlags, InjectFlags as InjectFlags$1, ɵsetAllowDuplicateNgModuleIdsForTest, ɵresetCompiledComponents, ɵsetUnknownElementStrictMode as ɵsetUnknownElementStrictMode$1, ɵsetUnknownPropertyStrictMode as ɵsetUnknownPropertyStrictMode$1, ɵgetUnknownElementStrictMode as ɵgetUnknownElementStrictMode$1, ɵgetUnknownPropertyStrictMode as ɵgetUnknownPropertyStrictMode$1, EnvironmentInjector as EnvironmentInjector$1, NgZone as NgZone$1, ɵflushModuleScopingQueueAsMuchAsPossible } from '@angular/core';
- import { ResourceLoader } from '@angular/compiler';
- import { Subject, Subscription, Observable, merge as merge$1 } from 'rxjs';
- import { share } from 'rxjs/operators';
- /**
- * Wraps a test function in an asynchronous test zone. The test will automatically
- * complete when all asynchronous calls within this zone are done. Can be used
- * to wrap an {@link inject} call.
- *
- * Example:
- *
- * ```
- * it('...', waitForAsync(inject([AClass], (object) => {
- * object.doSomething.then(() => {
- * expect(...);
- * })
- * });
- * ```
- *
- * @publicApi
- */
- function waitForAsync(fn) {
- const _Zone = typeof Zone !== 'undefined' ? Zone : null;
- if (!_Zone) {
- return function () {
- return Promise.reject('Zone is needed for the waitForAsync() test helper but could not be found. ' +
- 'Please make sure that your environment includes zone.js');
- };
- }
- const asyncTest = _Zone && _Zone[_Zone.__symbol__('asyncTest')];
- if (typeof asyncTest === 'function') {
- return asyncTest(fn);
- }
- return function () {
- return Promise.reject('zone-testing.js is needed for the async() test helper but could not be found. ' +
- 'Please make sure that your environment includes zone.js/testing');
- };
- }
- /**
- * @deprecated use `waitForAsync()`, (expected removal in v12)
- * @see {@link waitForAsync}
- * @publicApi
- * */
- function async(fn) {
- return waitForAsync(fn);
- }
- /**
- * Fixture for debugging and testing a component.
- *
- * @publicApi
- */
- class ComponentFixture {
- constructor(componentRef, ngZone, _autoDetect) {
- this.componentRef = componentRef;
- this.ngZone = ngZone;
- this._autoDetect = _autoDetect;
- this._isStable = true;
- this._isDestroyed = false;
- this._resolve = null;
- this._promise = null;
- this._onUnstableSubscription = null;
- this._onStableSubscription = null;
- this._onMicrotaskEmptySubscription = null;
- this._onErrorSubscription = null;
- this.changeDetectorRef = componentRef.changeDetectorRef;
- this.elementRef = componentRef.location;
- this.debugElement = getDebugNode(this.elementRef.nativeElement);
- this.componentInstance = componentRef.instance;
- this.nativeElement = this.elementRef.nativeElement;
- this.componentRef = componentRef;
- this.ngZone = ngZone;
- if (ngZone) {
- // Create subscriptions outside the NgZone so that the callbacks run oustide
- // of NgZone.
- ngZone.runOutsideAngular(() => {
- this._onUnstableSubscription = ngZone.onUnstable.subscribe({
- next: () => {
- this._isStable = false;
- }
- });
- this._onMicrotaskEmptySubscription = ngZone.onMicrotaskEmpty.subscribe({
- next: () => {
- if (this._autoDetect) {
- // Do a change detection run with checkNoChanges set to true to check
- // there are no changes on the second run.
- this.detectChanges(true);
- }
- }
- });
- this._onStableSubscription = ngZone.onStable.subscribe({
- next: () => {
- this._isStable = true;
- // Check whether there is a pending whenStable() completer to resolve.
- if (this._promise !== null) {
- // If so check whether there are no pending macrotasks before resolving.
- // Do this check in the next tick so that ngZone gets a chance to update the state of
- // pending macrotasks.
- queueMicrotask(() => {
- if (!ngZone.hasPendingMacrotasks) {
- if (this._promise !== null) {
- this._resolve(true);
- this._resolve = null;
- this._promise = null;
- }
- }
- });
- }
- }
- });
- this._onErrorSubscription = ngZone.onError.subscribe({
- next: (error) => {
- throw error;
- }
- });
- });
- }
- }
- _tick(checkNoChanges) {
- this.changeDetectorRef.detectChanges();
- if (checkNoChanges) {
- this.checkNoChanges();
- }
- }
- /**
- * Trigger a change detection cycle for the component.
- */
- detectChanges(checkNoChanges = true) {
- if (this.ngZone != null) {
- // Run the change detection inside the NgZone so that any async tasks as part of the change
- // detection are captured by the zone and can be waited for in isStable.
- this.ngZone.run(() => {
- this._tick(checkNoChanges);
- });
- }
- else {
- // Running without zone. Just do the change detection.
- this._tick(checkNoChanges);
- }
- }
- /**
- * Do a change detection run to make sure there were no changes.
- */
- checkNoChanges() {
- this.changeDetectorRef.checkNoChanges();
- }
- /**
- * Set whether the fixture should autodetect changes.
- *
- * Also runs detectChanges once so that any existing change is detected.
- */
- autoDetectChanges(autoDetect = true) {
- if (this.ngZone == null) {
- throw new Error('Cannot call autoDetectChanges when ComponentFixtureNoNgZone is set');
- }
- this._autoDetect = autoDetect;
- this.detectChanges();
- }
- /**
- * Return whether the fixture is currently stable or has async tasks that have not been completed
- * yet.
- */
- isStable() {
- return this._isStable && !this.ngZone.hasPendingMacrotasks;
- }
- /**
- * Get a promise that resolves when the fixture is stable.
- *
- * This can be used to resume testing after events have triggered asynchronous activity or
- * asynchronous change detection.
- */
- whenStable() {
- if (this.isStable()) {
- return Promise.resolve(false);
- }
- else if (this._promise !== null) {
- return this._promise;
- }
- else {
- this._promise = new Promise(res => {
- this._resolve = res;
- });
- return this._promise;
- }
- }
- _getRenderer() {
- if (this._renderer === undefined) {
- this._renderer = this.componentRef.injector.get(RendererFactory2$1, null);
- }
- return this._renderer;
- }
- /**
- * Get a promise that resolves when the ui state is stable following animations.
- */
- whenRenderingDone() {
- const renderer = this._getRenderer();
- if (renderer && renderer.whenRenderingDone) {
- return renderer.whenRenderingDone();
- }
- return this.whenStable();
- }
- /**
- * Trigger component destruction.
- */
- destroy() {
- if (!this._isDestroyed) {
- this.componentRef.destroy();
- if (this._onUnstableSubscription != null) {
- this._onUnstableSubscription.unsubscribe();
- this._onUnstableSubscription = null;
- }
- if (this._onStableSubscription != null) {
- this._onStableSubscription.unsubscribe();
- this._onStableSubscription = null;
- }
- if (this._onMicrotaskEmptySubscription != null) {
- this._onMicrotaskEmptySubscription.unsubscribe();
- this._onMicrotaskEmptySubscription = null;
- }
- if (this._onErrorSubscription != null) {
- this._onErrorSubscription.unsubscribe();
- this._onErrorSubscription = null;
- }
- this._isDestroyed = true;
- }
- }
- }
- const _Zone = typeof Zone !== 'undefined' ? Zone : null;
- const fakeAsyncTestModule = _Zone && _Zone[_Zone.__symbol__('fakeAsyncTest')];
- const fakeAsyncTestModuleNotLoadedErrorMessage = `zone-testing.js is needed for the fakeAsync() test helper but could not be found.
- Please make sure that your environment includes zone.js/testing`;
- /**
- * Clears out the shared fake async zone for a test.
- * To be called in a global `beforeEach`.
- *
- * @publicApi
- */
- function resetFakeAsyncZone() {
- if (fakeAsyncTestModule) {
- return fakeAsyncTestModule.resetFakeAsyncZone();
- }
- throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
- }
- /**
- * Wraps a function to be executed in the `fakeAsync` zone:
- * - Microtasks are manually executed by calling `flushMicrotasks()`.
- * - Timers are synchronous; `tick()` simulates the asynchronous passage of time.
- *
- * If there are any pending timers at the end of the function, an exception is thrown.
- *
- * Can be used to wrap `inject()` calls.
- *
- * @param fn The function that you want to wrap in the `fakeAsync` zone.
- *
- * @usageNotes
- * ### Example
- *
- * {@example core/testing/ts/fake_async.ts region='basic'}
- *
- *
- * @returns The function wrapped to be executed in the `fakeAsync` zone.
- * Any arguments passed when calling this returned function will be passed through to the `fn`
- * function in the parameters when it is called.
- *
- * @publicApi
- */
- function fakeAsync(fn) {
- if (fakeAsyncTestModule) {
- return fakeAsyncTestModule.fakeAsync(fn);
- }
- throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
- }
- /**
- * Simulates the asynchronous passage of time for the timers in the `fakeAsync` zone.
- *
- * The microtasks queue is drained at the very start of this function and after any timer callback
- * has been executed.
- *
- * @param millis The number of milliseconds to advance the virtual timer.
- * @param tickOptions The options to pass to the `tick()` function.
- *
- * @usageNotes
- *
- * The `tick()` option is a flag called `processNewMacroTasksSynchronously`,
- * which determines whether or not to invoke new macroTasks.
- *
- * If you provide a `tickOptions` object, but do not specify a
- * `processNewMacroTasksSynchronously` property (`tick(100, {})`),
- * then `processNewMacroTasksSynchronously` defaults to true.
- *
- * If you omit the `tickOptions` parameter (`tick(100))`), then
- * `tickOptions` defaults to `{processNewMacroTasksSynchronously: true}`.
- *
- * ### Example
- *
- * {@example core/testing/ts/fake_async.ts region='basic'}
- *
- * The following example includes a nested timeout (new macroTask), and
- * the `tickOptions` parameter is allowed to default. In this case,
- * `processNewMacroTasksSynchronously` defaults to true, and the nested
- * function is executed on each tick.
- *
- * ```
- * it ('test with nested setTimeout', fakeAsync(() => {
- * let nestedTimeoutInvoked = false;
- * function funcWithNestedTimeout() {
- * setTimeout(() => {
- * nestedTimeoutInvoked = true;
- * });
- * };
- * setTimeout(funcWithNestedTimeout);
- * tick();
- * expect(nestedTimeoutInvoked).toBe(true);
- * }));
- * ```
- *
- * In the following case, `processNewMacroTasksSynchronously` is explicitly
- * set to false, so the nested timeout function is not invoked.
- *
- * ```
- * it ('test with nested setTimeout', fakeAsync(() => {
- * let nestedTimeoutInvoked = false;
- * function funcWithNestedTimeout() {
- * setTimeout(() => {
- * nestedTimeoutInvoked = true;
- * });
- * };
- * setTimeout(funcWithNestedTimeout);
- * tick(0, {processNewMacroTasksSynchronously: false});
- * expect(nestedTimeoutInvoked).toBe(false);
- * }));
- * ```
- *
- *
- * @publicApi
- */
- function tick(millis = 0, tickOptions = {
- processNewMacroTasksSynchronously: true
- }) {
- if (fakeAsyncTestModule) {
- return fakeAsyncTestModule.tick(millis, tickOptions);
- }
- throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
- }
- /**
- * Flushes any pending microtasks and simulates the asynchronous passage of time for the timers in
- * the `fakeAsync` zone by
- * draining the macrotask queue until it is empty.
- *
- * @param maxTurns The maximum number of times the scheduler attempts to clear its queue before
- * throwing an error.
- * @returns The simulated time elapsed, in milliseconds.
- *
- * @publicApi
- */
- function flush(maxTurns) {
- if (fakeAsyncTestModule) {
- return fakeAsyncTestModule.flush(maxTurns);
- }
- throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
- }
- /**
- * Discard all remaining periodic tasks.
- *
- * @publicApi
- */
- function discardPeriodicTasks() {
- if (fakeAsyncTestModule) {
- return fakeAsyncTestModule.discardPeriodicTasks();
- }
- throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
- }
- /**
- * Flush any pending microtasks.
- *
- * @publicApi
- */
- function flushMicrotasks() {
- if (fakeAsyncTestModule) {
- return fakeAsyncTestModule.flushMicrotasks();
- }
- throw new Error(fakeAsyncTestModuleNotLoadedErrorMessage);
- }
- /** Whether test modules should be torn down by default. */
- const TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT = true;
- /** Whether unknown elements in templates should throw by default. */
- const THROW_ON_UNKNOWN_ELEMENTS_DEFAULT = false;
- /** Whether unknown properties in templates should throw by default. */
- const THROW_ON_UNKNOWN_PROPERTIES_DEFAULT = false;
- /**
- * An abstract class for inserting the root test component element in a platform independent way.
- *
- * @publicApi
- */
- class TestComponentRenderer {
- insertRootElement(rootElementId) { }
- removeAllRootElements() { }
- }
- /**
- * @publicApi
- */
- const ComponentFixtureAutoDetect = new InjectionToken$1('ComponentFixtureAutoDetect');
- /**
- * @publicApi
- */
- const ComponentFixtureNoNgZone = new InjectionToken$1('ComponentFixtureNoNgZone');
- /**
- * Used to resolve resource URLs on `@Component` when used with JIT compilation.
- *
- * Example:
- * ```
- * @Component({
- * selector: 'my-comp',
- * templateUrl: 'my-comp.html', // This requires asynchronous resolution
- * })
- * class MyComponent{
- * }
- *
- * // Calling `renderComponent` will fail because `renderComponent` is a synchronous process
- * // and `MyComponent`'s `@Component.templateUrl` needs to be resolved asynchronously.
- *
- * // Calling `resolveComponentResources()` will resolve `@Component.templateUrl` into
- * // `@Component.template`, which allows `renderComponent` to proceed in a synchronous manner.
- *
- * // Use browser's `fetch()` function as the default resource resolution strategy.
- * resolveComponentResources(fetch).then(() => {
- * // After resolution all URLs have been converted into `template` strings.
- * renderComponent(MyComponent);
- * });
- *
- * ```
- *
- * NOTE: In AOT the resolution happens during compilation, and so there should be no need
- * to call this method outside JIT mode.
- *
- * @param resourceResolver a function which is responsible for returning a `Promise` to the
- * contents of the resolved URL. Browser's `fetch()` method is a good default implementation.
- */
- function resolveComponentResources(resourceResolver) {
- // Store all promises which are fetching the resources.
- const componentResolved = [];
- // Cache so that we don't fetch the same resource more than once.
- const urlMap = new Map();
- function cachedResourceResolve(url) {
- let promise = urlMap.get(url);
- if (!promise) {
- const resp = resourceResolver(url);
- urlMap.set(url, promise = resp.then(unwrapResponse));
- }
- return promise;
- }
- componentResourceResolutionQueue.forEach((component, type) => {
- const promises = [];
- if (component.templateUrl) {
- promises.push(cachedResourceResolve(component.templateUrl).then((template) => {
- component.template = template;
- }));
- }
- const styleUrls = component.styleUrls;
- const styles = component.styles || (component.styles = []);
- const styleOffset = component.styles.length;
- styleUrls && styleUrls.forEach((styleUrl, index) => {
- styles.push(''); // pre-allocate array.
- promises.push(cachedResourceResolve(styleUrl).then((style) => {
- styles[styleOffset + index] = style;
- styleUrls.splice(styleUrls.indexOf(styleUrl), 1);
- if (styleUrls.length == 0) {
- component.styleUrls = undefined;
- }
- }));
- });
- const fullyResolved = Promise.all(promises).then(() => componentDefResolved(type));
- componentResolved.push(fullyResolved);
- });
- clearResolutionOfComponentResourcesQueue();
- return Promise.all(componentResolved).then(() => undefined);
- }
- let componentResourceResolutionQueue = new Map();
- // Track when existing ɵcmp for a Type is waiting on resources.
- const componentDefPendingResolution = new Set();
- function maybeQueueResolutionOfComponentResources(type, metadata) {
- if (componentNeedsResolution(metadata)) {
- componentResourceResolutionQueue.set(type, metadata);
- componentDefPendingResolution.add(type);
- }
- }
- function isComponentDefPendingResolution(type) {
- return componentDefPendingResolution.has(type);
- }
- function componentNeedsResolution(component) {
- return !!((component.templateUrl && !component.hasOwnProperty('template')) ||
- component.styleUrls && component.styleUrls.length);
- }
- function clearResolutionOfComponentResourcesQueue() {
- const old = componentResourceResolutionQueue;
- componentResourceResolutionQueue = new Map();
- return old;
- }
- function restoreComponentResolutionQueue(queue) {
- componentDefPendingResolution.clear();
- queue.forEach((_, type) => componentDefPendingResolution.add(type));
- componentResourceResolutionQueue = queue;
- }
- function isComponentResourceResolutionQueueEmpty() {
- return componentResourceResolutionQueue.size === 0;
- }
- function unwrapResponse(response) {
- return typeof response == 'string' ? response : response.text();
- }
- function componentDefResolved(type) {
- componentDefPendingResolution.delete(type);
- }
- const _global = globalThis;
- var FactoryTarget;
- (function (FactoryTarget) {
- FactoryTarget[FactoryTarget["Directive"] = 0] = "Directive";
- FactoryTarget[FactoryTarget["Component"] = 1] = "Component";
- FactoryTarget[FactoryTarget["Injectable"] = 2] = "Injectable";
- FactoryTarget[FactoryTarget["Pipe"] = 3] = "Pipe";
- FactoryTarget[FactoryTarget["NgModule"] = 4] = "NgModule";
- })(FactoryTarget || (FactoryTarget = {}));
- var R3TemplateDependencyKind;
- (function (R3TemplateDependencyKind) {
- R3TemplateDependencyKind[R3TemplateDependencyKind["Directive"] = 0] = "Directive";
- R3TemplateDependencyKind[R3TemplateDependencyKind["Pipe"] = 1] = "Pipe";
- R3TemplateDependencyKind[R3TemplateDependencyKind["NgModule"] = 2] = "NgModule";
- })(R3TemplateDependencyKind || (R3TemplateDependencyKind = {}));
- var ViewEncapsulation$1;
- (function (ViewEncapsulation) {
- ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
- // Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
- ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
- ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
- })(ViewEncapsulation$1 || (ViewEncapsulation$1 = {}));
- function getCompilerFacade(request) {
- const globalNg = _global['ng'];
- if (globalNg && globalNg.ɵcompilerFacade) {
- return globalNg.ɵcompilerFacade;
- }
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- // Log the type as an error so that a developer can easily navigate to the type from the
- // console.
- console.error(`JIT compilation failed for ${request.kind}`, request.type);
- let message = `The ${request.kind} '${request
- .type.name}' needs to be compiled using the JIT compiler, but '@angular/compiler' is not available.\n\n`;
- if (request.usage === 1 /* JitCompilerUsage.PartialDeclaration */) {
- message += `The ${request.kind} is part of a library that has been partially compiled.\n`;
- message +=
- `However, the Angular Linker has not processed the library such that JIT compilation is used as fallback.\n`;
- message += '\n';
- message +=
- `Ideally, the library is processed using the Angular Linker to become fully AOT compiled.\n`;
- }
- else {
- message +=
- `JIT compilation is discouraged for production use-cases! Consider using AOT mode instead.\n`;
- }
- message +=
- `Alternatively, the JIT compiler should be loaded by bootstrapping using '@angular/platform-browser-dynamic' or '@angular/platform-server',\n`;
- message +=
- `or manually provide the compiler with 'import "@angular/compiler";' before bootstrapping.`;
- throw new Error(message);
- }
- else {
- throw new Error('JIT compiler unavailable');
- }
- }
- function getClosureSafeProperty(objWithPropertyToExtract) {
- for (let key in objWithPropertyToExtract) {
- if (objWithPropertyToExtract[key] === getClosureSafeProperty) {
- return key;
- }
- }
- throw Error('Could not find renamed property on target object.');
- }
- /**
- * Sets properties on a target object from a source object, but only if
- * the property doesn't already exist on the target object.
- * @param target The target to set properties on
- * @param source The source of the property keys and values to set
- */
- function fillProperties(target, source) {
- for (const key in source) {
- if (source.hasOwnProperty(key) && !target.hasOwnProperty(key)) {
- target[key] = source[key];
- }
- }
- }
- function stringify(token) {
- if (typeof token === 'string') {
- return token;
- }
- if (Array.isArray(token)) {
- return '[' + token.map(stringify).join(', ') + ']';
- }
- if (token == null) {
- return '' + token;
- }
- if (token.overriddenName) {
- return `${token.overriddenName}`;
- }
- if (token.name) {
- return `${token.name}`;
- }
- const res = token.toString();
- if (res == null) {
- return '' + res;
- }
- const newLineIndex = res.indexOf('\n');
- return newLineIndex === -1 ? res : res.substring(0, newLineIndex);
- }
- /**
- * Concatenates two strings with separator, allocating new strings only when necessary.
- *
- * @param before before string.
- * @param separator separator string.
- * @param after after string.
- * @returns concatenated string.
- */
- function concatStringsWithSpace(before, after) {
- return (before == null || before === '') ?
- (after === null ? '' : after) :
- ((after == null || after === '') ? before : before + ' ' + after);
- }
- const __forward_ref__ = getClosureSafeProperty({ __forward_ref__: getClosureSafeProperty });
- /**
- * Allows to refer to references which are not yet defined.
- *
- * For instance, `forwardRef` is used when the `token` which we need to refer to for the purposes of
- * DI is declared, but not yet defined. It is also used when the `token` which we use when creating
- * a query is not yet defined.
- *
- * `forwardRef` is also used to break circularities in standalone components imports.
- *
- * @usageNotes
- * ### Circular dependency example
- * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='forward_ref'}
- *
- * ### Circular standalone reference import example
- * ```ts
- * @Component({
- * standalone: true,
- * imports: [ChildComponent],
- * selector: 'app-parent',
- * template: `<app-child [hideParent]="hideParent"></app-child>`,
- * })
- * export class ParentComponent {
- * @Input() hideParent: boolean;
- * }
- *
- *
- * @Component({
- * standalone: true,
- * imports: [CommonModule, forwardRef(() => ParentComponent)],
- * selector: 'app-child',
- * template: `<app-parent *ngIf="!hideParent"></app-parent>`,
- * })
- * export class ChildComponent {
- * @Input() hideParent: boolean;
- * }
- * ```
- *
- * @publicApi
- */
- function forwardRef(forwardRefFn) {
- forwardRefFn.__forward_ref__ = forwardRef;
- forwardRefFn.toString = function () {
- return stringify(this());
- };
- return forwardRefFn;
- }
- /**
- * Lazily retrieves the reference value from a forwardRef.
- *
- * Acts as the identity function when given a non-forward-ref value.
- *
- * @usageNotes
- * ### Example
- *
- * {@example core/di/ts/forward_ref/forward_ref_spec.ts region='resolve_forward_ref'}
- *
- * @see {@link forwardRef}
- * @publicApi
- */
- function resolveForwardRef(type) {
- return isForwardRef(type) ? type() : type;
- }
- /** Checks whether a function is wrapped by a `forwardRef`. */
- function isForwardRef(fn) {
- return typeof fn === 'function' && fn.hasOwnProperty(__forward_ref__) &&
- fn.__forward_ref__ === forwardRef;
- }
- /**
- * Construct an injectable definition which defines how a token will be constructed by the DI
- * system, and in which injectors (if any) it will be available.
- *
- * This should be assigned to a static `ɵprov` field on a type, which will then be an
- * `InjectableType`.
- *
- * Options:
- * * `providedIn` determines which injectors will include the injectable, by either associating it
- * with an `@NgModule` or other `InjectorType`, or by specifying that this injectable should be
- * provided in the `'root'` injector, which will be the application-level injector in most apps.
- * * `factory` gives the zero argument function which will create an instance of the injectable.
- * The factory can call [`inject`](api/core/inject) to access the `Injector` and request injection
- * of dependencies.
- *
- * @codeGenApi
- * @publicApi This instruction has been emitted by ViewEngine for some time and is deployed to npm.
- */
- function ɵɵdefineInjectable(opts) {
- return {
- token: opts.token,
- providedIn: opts.providedIn || null,
- factory: opts.factory,
- value: undefined,
- };
- }
- /**
- * @deprecated in v8, delete after v10. This API should be used only by generated code, and that
- * code should now use ɵɵdefineInjectable instead.
- * @publicApi
- */
- const defineInjectable = ɵɵdefineInjectable;
- /**
- * Construct an `InjectorDef` which configures an injector.
- *
- * This should be assigned to a static injector def (`ɵinj`) field on a type, which will then be an
- * `InjectorType`.
- *
- * Options:
- *
- * * `providers`: an optional array of providers to add to the injector. Each provider must
- * either have a factory or point to a type which has a `ɵprov` static property (the
- * type must be an `InjectableType`).
- * * `imports`: an optional array of imports of other `InjectorType`s or `InjectorTypeWithModule`s
- * whose providers will also be added to the injector. Locally provided types will override
- * providers from imports.
- *
- * @codeGenApi
- */
- function ɵɵdefineInjector(options) {
- return { providers: options.providers || [], imports: options.imports || [] };
- }
- /**
- * Read the injectable def (`ɵprov`) for `type` in a way which is immune to accidentally reading
- * inherited value.
- *
- * @param type A type which may have its own (non-inherited) `ɵprov`.
- */
- function getInjectableDef(type) {
- return getOwnDefinition(type, NG_PROV_DEF) || getOwnDefinition(type, NG_INJECTABLE_DEF);
- }
- function isInjectable(type) {
- return getInjectableDef(type) !== null;
- }
- /**
- * Return definition only if it is defined directly on `type` and is not inherited from a base
- * class of `type`.
- */
- function getOwnDefinition(type, field) {
- return type.hasOwnProperty(field) ? type[field] : null;
- }
- /**
- * Read the injectable def (`ɵprov`) for `type` or read the `ɵprov` from one of its ancestors.
- *
- * @param type A type which may have `ɵprov`, via inheritance.
- *
- * @deprecated Will be removed in a future version of Angular, where an error will occur in the
- * scenario if we find the `ɵprov` on an ancestor only.
- */
- function getInheritedInjectableDef(type) {
- const def = type && (type[NG_PROV_DEF] || type[NG_INJECTABLE_DEF]);
- if (def) {
- ngDevMode &&
- console.warn(`DEPRECATED: DI is instantiating a token "${type.name}" that inherits its @Injectable decorator but does not provide one itself.\n` +
- `This will become an error in a future version of Angular. Please add @Injectable() to the "${type.name}" class.`);
- return def;
- }
- else {
- return null;
- }
- }
- /**
- * Read the injector def type in a way which is immune to accidentally reading inherited value.
- *
- * @param type type which may have an injector def (`ɵinj`)
- */
- function getInjectorDef(type) {
- return type && (type.hasOwnProperty(NG_INJ_DEF) || type.hasOwnProperty(NG_INJECTOR_DEF)) ?
- type[NG_INJ_DEF] :
- null;
- }
- const NG_PROV_DEF = getClosureSafeProperty({ ɵprov: getClosureSafeProperty });
- const NG_INJ_DEF = getClosureSafeProperty({ ɵinj: getClosureSafeProperty });
- // We need to keep these around so we can read off old defs if new defs are unavailable
- const NG_INJECTABLE_DEF = getClosureSafeProperty({ ngInjectableDef: getClosureSafeProperty });
- const NG_INJECTOR_DEF = getClosureSafeProperty({ ngInjectorDef: getClosureSafeProperty });
- /**
- * Base URL for the error details page.
- *
- * Keep this constant in sync across:
- * - packages/compiler-cli/src/ngtsc/diagnostics/src/error_details_base_url.ts
- * - packages/core/src/error_details_base_url.ts
- */
- const ERROR_DETAILS_PAGE_BASE_URL = 'https://angular.io/errors';
- /**
- * URL for the XSS security documentation.
- */
- const XSS_SECURITY_URL = 'https://g.co/ng/security#xss';
- /**
- * Class that represents a runtime error.
- * Formats and outputs the error message in a consistent way.
- *
- * Example:
- * ```
- * throw new RuntimeError(
- * RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED,
- * ngDevMode && 'Injector has already been destroyed.');
- * ```
- *
- * Note: the `message` argument contains a descriptive error message as a string in development
- * mode (when the `ngDevMode` is defined). In production mode (after tree-shaking pass), the
- * `message` argument becomes `false`, thus we account for it in the typings and the runtime
- * logic.
- */
- class RuntimeError extends Error {
- constructor(code, message) {
- super(formatRuntimeError(code, message));
- this.code = code;
- }
- }
- /**
- * Called to format a runtime error.
- * See additional info on the `message` argument type in the `RuntimeError` class description.
- */
- function formatRuntimeError(code, message) {
- // Error code might be a negative number, which is a special marker that instructs the logic to
- // generate a link to the error details page on angular.io.
- // We also prepend `0` to non-compile-time errors.
- const fullCode = `NG0${Math.abs(code)}`;
- let errorMessage = `${fullCode}${message ? ': ' + message : ''}`;
- if (ngDevMode && code < 0) {
- const addPeriodSeparator = !errorMessage.match(/[.,;!?\n]$/);
- const separator = addPeriodSeparator ? '.' : '';
- errorMessage =
- `${errorMessage}${separator} Find more at ${ERROR_DETAILS_PAGE_BASE_URL}/${fullCode}`;
- }
- return errorMessage;
- }
- /**
- * @description
- *
- * Represents a type that a Component or other object is instances of.
- *
- * An example of a `Type` is `MyCustomComponent` class, which in JavaScript is represented by
- * the `MyCustomComponent` constructor function.
- *
- * @publicApi
- */
- const Type = Function;
- function isType(v) {
- return typeof v === 'function';
- }
- // The functions in this file verify that the assumptions we are making
- function assertNumber(actual, msg) {
- if (!(typeof actual === 'number')) {
- throwError(msg, typeof actual, 'number', '===');
- }
- }
- function assertNumberInRange(actual, minInclusive, maxInclusive) {
- assertNumber(actual, 'Expected a number');
- assertLessThanOrEqual(actual, maxInclusive, 'Expected number to be less than or equal to');
- assertGreaterThanOrEqual(actual, minInclusive, 'Expected number to be greater than or equal to');
- }
- function assertString(actual, msg) {
- if (!(typeof actual === 'string')) {
- throwError(msg, actual === null ? 'null' : typeof actual, 'string', '===');
- }
- }
- function assertFunction(actual, msg) {
- if (!(typeof actual === 'function')) {
- throwError(msg, actual === null ? 'null' : typeof actual, 'function', '===');
- }
- }
- function assertEqual(actual, expected, msg) {
- if (!(actual == expected)) {
- throwError(msg, actual, expected, '==');
- }
- }
- function assertNotEqual(actual, expected, msg) {
- if (!(actual != expected)) {
- throwError(msg, actual, expected, '!=');
- }
- }
- function assertSame(actual, expected, msg) {
- if (!(actual === expected)) {
- throwError(msg, actual, expected, '===');
- }
- }
- function assertNotSame(actual, expected, msg) {
- if (!(actual !== expected)) {
- throwError(msg, actual, expected, '!==');
- }
- }
- function assertLessThan(actual, expected, msg) {
- if (!(actual < expected)) {
- throwError(msg, actual, expected, '<');
- }
- }
- function assertLessThanOrEqual(actual, expected, msg) {
- if (!(actual <= expected)) {
- throwError(msg, actual, expected, '<=');
- }
- }
- function assertGreaterThan(actual, expected, msg) {
- if (!(actual > expected)) {
- throwError(msg, actual, expected, '>');
- }
- }
- function assertGreaterThanOrEqual(actual, expected, msg) {
- if (!(actual >= expected)) {
- throwError(msg, actual, expected, '>=');
- }
- }
- function assertNotDefined(actual, msg) {
- if (actual != null) {
- throwError(msg, actual, null, '==');
- }
- }
- function assertDefined(actual, msg) {
- if (actual == null) {
- throwError(msg, actual, null, '!=');
- }
- }
- function throwError(msg, actual, expected, comparison) {
- throw new Error(`ASSERTION ERROR: ${msg}` +
- (comparison == null ? '' : ` [Expected=> ${expected} ${comparison} ${actual} <=Actual]`));
- }
- function assertDomNode(node) {
- if (!(node instanceof Node)) {
- throwError(`The provided value must be an instance of a DOM Node but got ${stringify(node)}`);
- }
- }
- function assertIndexInRange(arr, index) {
- assertDefined(arr, 'Array must be defined.');
- const maxLen = arr.length;
- if (index < 0 || index >= maxLen) {
- throwError(`Index expected to be less than ${maxLen} but got ${index}`);
- }
- }
- function assertOneOf(value, ...validValues) {
- if (validValues.indexOf(value) !== -1)
- return true;
- throwError(`Expected value to be one of ${JSON.stringify(validValues)} but was ${JSON.stringify(value)}.`);
- }
- /**
- * Determines if the contents of two arrays is identical
- *
- * @param a first array
- * @param b second array
- * @param identityAccessor Optional function for extracting stable object identity from a value in
- * the array.
- */
- function arrayEquals(a, b, identityAccessor) {
- if (a.length !== b.length)
- return false;
- for (let i = 0; i < a.length; i++) {
- let valueA = a[i];
- let valueB = b[i];
- if (identityAccessor) {
- valueA = identityAccessor(valueA);
- valueB = identityAccessor(valueB);
- }
- if (valueB !== valueA) {
- return false;
- }
- }
- return true;
- }
- /**
- * Flattens an array.
- */
- function flatten$1(list) {
- return list.flat(Number.POSITIVE_INFINITY);
- }
- function deepForEach(input, fn) {
- input.forEach(value => Array.isArray(value) ? deepForEach(value, fn) : fn(value));
- }
- function addToArray(arr, index, value) {
- // perf: array.push is faster than array.splice!
- if (index >= arr.length) {
- arr.push(value);
- }
- else {
- arr.splice(index, 0, value);
- }
- }
- function removeFromArray(arr, index) {
- // perf: array.pop is faster than array.splice!
- if (index >= arr.length - 1) {
- return arr.pop();
- }
- else {
- return arr.splice(index, 1)[0];
- }
- }
- function newArray(size, value) {
- const list = [];
- for (let i = 0; i < size; i++) {
- list.push(value);
- }
- return list;
- }
- /**
- * Remove item from array (Same as `Array.splice()` but faster.)
- *
- * `Array.splice()` is not as fast because it has to allocate an array for the elements which were
- * removed. This causes memory pressure and slows down code when most of the time we don't
- * care about the deleted items array.
- *
- * https://jsperf.com/fast-array-splice (About 20x faster)
- *
- * @param array Array to splice
- * @param index Index of element in array to remove.
- * @param count Number of items to remove.
- */
- function arraySplice(array, index, count) {
- const length = array.length - count;
- while (index < length) {
- array[index] = array[index + count];
- index++;
- }
- while (count--) {
- array.pop(); // shrink the array
- }
- }
- /**
- * Same as `Array.splice(index, 0, value)` but faster.
- *
- * `Array.splice()` is not fast because it has to allocate an array for the elements which were
- * removed. This causes memory pressure and slows down code when most of the time we don't
- * care about the deleted items array.
- *
- * @param array Array to splice.
- * @param index Index in array where the `value` should be added.
- * @param value Value to add to array.
- */
- function arrayInsert(array, index, value) {
- ngDevMode && assertLessThanOrEqual(index, array.length, 'Can\'t insert past array end.');
- let end = array.length;
- while (end > index) {
- const previousEnd = end - 1;
- array[end] = array[previousEnd];
- end = previousEnd;
- }
- array[index] = value;
- }
- /**
- * Same as `Array.splice2(index, 0, value1, value2)` but faster.
- *
- * `Array.splice()` is not fast because it has to allocate an array for the elements which were
- * removed. This causes memory pressure and slows down code when most of the time we don't
- * care about the deleted items array.
- *
- * @param array Array to splice.
- * @param index Index in array where the `value` should be added.
- * @param value1 Value to add to array.
- * @param value2 Value to add to array.
- */
- function arrayInsert2(array, index, value1, value2) {
- ngDevMode && assertLessThanOrEqual(index, array.length, 'Can\'t insert past array end.');
- let end = array.length;
- if (end == index) {
- // inserting at the end.
- array.push(value1, value2);
- }
- else if (end === 1) {
- // corner case when we have less items in array than we have items to insert.
- array.push(value2, array[0]);
- array[0] = value1;
- }
- else {
- end--;
- array.push(array[end - 1], array[end]);
- while (end > index) {
- const previousEnd = end - 2;
- array[end] = array[previousEnd];
- end--;
- }
- array[index] = value1;
- array[index + 1] = value2;
- }
- }
- /**
- * Get an index of an `value` in a sorted `array`.
- *
- * NOTE:
- * - This uses binary search algorithm for fast removals.
- *
- * @param array A sorted array to binary search.
- * @param value The value to look for.
- * @returns index of the value.
- * - positive index if value found.
- * - negative index if value not found. (`~index` to get the value where it should have been
- * located)
- */
- function arrayIndexOfSorted(array, value) {
- return _arrayIndexOfSorted(array, value, 0);
- }
- /**
- * Set a `value` for a `key`.
- *
- * @param keyValueArray to modify.
- * @param key The key to locate or create.
- * @param value The value to set for a `key`.
- * @returns index (always even) of where the value vas set.
- */
- function keyValueArraySet(keyValueArray, key, value) {
- let index = keyValueArrayIndexOf(keyValueArray, key);
- if (index >= 0) {
- // if we found it set it.
- keyValueArray[index | 1] = value;
- }
- else {
- index = ~index;
- arrayInsert2(keyValueArray, index, key, value);
- }
- return index;
- }
- /**
- * Retrieve a `value` for a `key` (on `undefined` if not found.)
- *
- * @param keyValueArray to search.
- * @param key The key to locate.
- * @return The `value` stored at the `key` location or `undefined if not found.
- */
- function keyValueArrayGet(keyValueArray, key) {
- const index = keyValueArrayIndexOf(keyValueArray, key);
- if (index >= 0) {
- // if we found it retrieve it.
- return keyValueArray[index | 1];
- }
- return undefined;
- }
- /**
- * Retrieve a `key` index value in the array or `-1` if not found.
- *
- * @param keyValueArray to search.
- * @param key The key to locate.
- * @returns index of where the key is (or should have been.)
- * - positive (even) index if key found.
- * - negative index if key not found. (`~index` (even) to get the index where it should have
- * been inserted.)
- */
- function keyValueArrayIndexOf(keyValueArray, key) {
- return _arrayIndexOfSorted(keyValueArray, key, 1);
- }
- /**
- * Delete a `key` (and `value`) from the `KeyValueArray`.
- *
- * @param keyValueArray to modify.
- * @param key The key to locate or delete (if exist).
- * @returns index of where the key was (or should have been.)
- * - positive (even) index if key found and deleted.
- * - negative index if key not found. (`~index` (even) to get the index where it should have
- * been.)
- */
- function keyValueArrayDelete(keyValueArray, key) {
- const index = keyValueArrayIndexOf(keyValueArray, key);
- if (index >= 0) {
- // if we found it remove it.
- arraySplice(keyValueArray, index, 2);
- }
- return index;
- }
- /**
- * INTERNAL: Get an index of an `value` in a sorted `array` by grouping search by `shift`.
- *
- * NOTE:
- * - This uses binary search algorithm for fast removals.
- *
- * @param array A sorted array to binary search.
- * @param value The value to look for.
- * @param shift grouping shift.
- * - `0` means look at every location
- * - `1` means only look at every other (even) location (the odd locations are to be ignored as
- * they are values.)
- * @returns index of the value.
- * - positive index if value found.
- * - negative index if value not found. (`~index` to get the value where it should have been
- * inserted)
- */
- function _arrayIndexOfSorted(array, value, shift) {
- ngDevMode && assertEqual(Array.isArray(array), true, 'Expecting an array');
- let start = 0;
- let end = array.length >> shift;
- while (end !== start) {
- const middle = start + ((end - start) >> 1); // find the middle.
- const current = array[middle << shift];
- if (value === current) {
- return (middle << shift);
- }
- else if (current > value) {
- end = middle;
- }
- else {
- start = middle + 1; // We already searched middle so make it non-inclusive by adding 1
- }
- }
- return ~(end << shift);
- }
- /**
- * Convince closure compiler that the wrapped function has no side-effects.
- *
- * Closure compiler always assumes that `toString` has no side-effects. We use this quirk to
- * allow us to execute a function but have closure compiler mark the call as no-side-effects.
- * It is important that the return value for the `noSideEffects` function be assigned
- * to something which is retained otherwise the call to `noSideEffects` will be removed by closure
- * compiler.
- */
- function noSideEffects(fn) {
- return { toString: fn }.toString();
- }
- const ANNOTATIONS = '__annotations__';
- const PARAMETERS = '__parameters__';
- const PROP_METADATA = '__prop__metadata__';
- /**
- * @suppress {globalThis}
- */
- function makeDecorator(name, props, parentClass, additionalProcessing, typeFn) {
- return noSideEffects(() => {
- const metaCtor = makeMetadataCtor(props);
- function DecoratorFactory(...args) {
- if (this instanceof DecoratorFactory) {
- metaCtor.call(this, ...args);
- return this;
- }
- const annotationInstance = new DecoratorFactory(...args);
- return function TypeDecorator(cls) {
- if (typeFn)
- typeFn(cls, ...args);
- // Use of Object.defineProperty is important since it creates non-enumerable property which
- // prevents the property is copied during subclassing.
- const annotations = cls.hasOwnProperty(ANNOTATIONS) ?
- cls[ANNOTATIONS] :
- Object.defineProperty(cls, ANNOTATIONS, { value: [] })[ANNOTATIONS];
- annotations.push(annotationInstance);
- if (additionalProcessing)
- additionalProcessing(cls);
- return cls;
- };
- }
- if (parentClass) {
- DecoratorFactory.prototype = Object.create(parentClass.prototype);
- }
- DecoratorFactory.prototype.ngMetadataName = name;
- DecoratorFactory.annotationCls = DecoratorFactory;
- return DecoratorFactory;
- });
- }
- function makeMetadataCtor(props) {
- return function ctor(...args) {
- if (props) {
- const values = props(...args);
- for (const propName in values) {
- this[propName] = values[propName];
- }
- }
- };
- }
- function makeParamDecorator(name, props, parentClass) {
- return noSideEffects(() => {
- const metaCtor = makeMetadataCtor(props);
- function ParamDecoratorFactory(...args) {
- if (this instanceof ParamDecoratorFactory) {
- metaCtor.apply(this, args);
- return this;
- }
- const annotationInstance = new ParamDecoratorFactory(...args);
- ParamDecorator.annotation = annotationInstance;
- return ParamDecorator;
- function ParamDecorator(cls, unusedKey, index) {
- // Use of Object.defineProperty is important since it creates non-enumerable property which
- // prevents the property is copied during subclassing.
- const parameters = cls.hasOwnProperty(PARAMETERS) ?
- cls[PARAMETERS] :
- Object.defineProperty(cls, PARAMETERS, { value: [] })[PARAMETERS];
- // there might be gaps if some in between parameters do not have annotations.
- // we pad with nulls.
- while (parameters.length <= index) {
- parameters.push(null);
- }
- (parameters[index] = parameters[index] || []).push(annotationInstance);
- return cls;
- }
- }
- if (parentClass) {
- ParamDecoratorFactory.prototype = Object.create(parentClass.prototype);
- }
- ParamDecoratorFactory.prototype.ngMetadataName = name;
- ParamDecoratorFactory.annotationCls = ParamDecoratorFactory;
- return ParamDecoratorFactory;
- });
- }
- function makePropDecorator(name, props, parentClass, additionalProcessing) {
- return noSideEffects(() => {
- const metaCtor = makeMetadataCtor(props);
- function PropDecoratorFactory(...args) {
- if (this instanceof PropDecoratorFactory) {
- metaCtor.apply(this, args);
- return this;
- }
- const decoratorInstance = new PropDecoratorFactory(...args);
- function PropDecorator(target, name) {
- // target is undefined with standard decorators. This case is not supported and will throw
- // if this decorator is used in JIT mode with standard decorators.
- if (target === undefined) {
- throw new Error('Standard Angular field decorators are not supported in JIT mode.');
- }
- const constructor = target.constructor;
- // Use of Object.defineProperty is important because it creates a non-enumerable property
- // which prevents the property from being copied during subclassing.
- const meta = constructor.hasOwnProperty(PROP_METADATA) ?
- constructor[PROP_METADATA] :
- Object.defineProperty(constructor, PROP_METADATA, { value: {} })[PROP_METADATA];
- meta[name] = meta.hasOwnProperty(name) && meta[name] || [];
- meta[name].unshift(decoratorInstance);
- if (additionalProcessing)
- additionalProcessing(target, name, ...args);
- }
- return PropDecorator;
- }
- if (parentClass) {
- PropDecoratorFactory.prototype = Object.create(parentClass.prototype);
- }
- PropDecoratorFactory.prototype.ngMetadataName = name;
- PropDecoratorFactory.annotationCls = PropDecoratorFactory;
- return PropDecoratorFactory;
- });
- }
- /*
- * #########################
- * Attention: These Regular expressions have to hold even if the code is minified!
- * ##########################
- */
- /**
- * Regular expression that detects pass-through constructors for ES5 output. This Regex
- * intends to capture the common delegation pattern emitted by TypeScript and Babel. Also
- * it intends to capture the pattern where existing constructors have been downleveled from
- * ES2015 to ES5 using TypeScript w/ downlevel iteration. e.g.
- *
- * ```
- * function MyClass() {
- * var _this = _super.apply(this, arguments) || this;
- * ```
- *
- * downleveled to ES5 with `downlevelIteration` for TypeScript < 4.2:
- * ```
- * function MyClass() {
- * var _this = _super.apply(this, __spread(arguments)) || this;
- * ```
- *
- * or downleveled to ES5 with `downlevelIteration` for TypeScript >= 4.2:
- * ```
- * function MyClass() {
- * var _this = _super.apply(this, __spreadArray([], __read(arguments), false)) || this;
- * ```
- *
- * More details can be found in: https://github.com/angular/angular/issues/38453.
- */
- const ES5_DELEGATE_CTOR = /^function\s+\S+\(\)\s*{[\s\S]+\.apply\(this,\s*(arguments|(?:[^()]+\(\[\],)?[^()]+\(arguments\).*)\)/;
- /** Regular expression that detects ES2015 classes which extend from other classes. */
- const ES2015_INHERITED_CLASS = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{/;
- /**
- * Regular expression that detects ES2015 classes which extend from other classes and
- * have an explicit constructor defined.
- */
- const ES2015_INHERITED_CLASS_WITH_CTOR = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(/;
- /**
- * Regular expression that detects ES2015 classes which extend from other classes
- * and inherit a constructor.
- */
- const ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR = /^class\s+[A-Za-z\d$_]*\s*extends\s+[^{]+{[\s\S]*constructor\s*\(\)\s*{[^}]*super\(\.\.\.arguments\)/;
- /**
- * Determine whether a stringified type is a class which delegates its constructor
- * to its parent.
- *
- * This is not trivial since compiled code can actually contain a constructor function
- * even if the original source code did not. For instance, when the child class contains
- * an initialized instance property.
- */
- function isDelegateCtor(typeStr) {
- return ES5_DELEGATE_CTOR.test(typeStr) ||
- ES2015_INHERITED_CLASS_WITH_DELEGATE_CTOR.test(typeStr) ||
- (ES2015_INHERITED_CLASS.test(typeStr) && !ES2015_INHERITED_CLASS_WITH_CTOR.test(typeStr));
- }
- class ReflectionCapabilities {
- constructor(reflect) {
- this._reflect = reflect || _global['Reflect'];
- }
- factory(t) {
- return (...args) => new t(...args);
- }
- /** @internal */
- _zipTypesAndAnnotations(paramTypes, paramAnnotations) {
- let result;
- if (typeof paramTypes === 'undefined') {
- result = newArray(paramAnnotations.length);
- }
- else {
- result = newArray(paramTypes.length);
- }
- for (let i = 0; i < result.length; i++) {
- // TS outputs Object for parameters without types, while Traceur omits
- // the annotations. For now we preserve the Traceur behavior to aid
- // migration, but this can be revisited.
- if (typeof paramTypes === 'undefined') {
- result[i] = [];
- }
- else if (paramTypes[i] && paramTypes[i] != Object) {
- result[i] = [paramTypes[i]];
- }
- else {
- result[i] = [];
- }
- if (paramAnnotations && paramAnnotations[i] != null) {
- result[i] = result[i].concat(paramAnnotations[i]);
- }
- }
- return result;
- }
- _ownParameters(type, parentCtor) {
- const typeStr = type.toString();
- // If we have no decorators, we only have function.length as metadata.
- // In that case, to detect whether a child class declared an own constructor or not,
- // we need to look inside of that constructor to check whether it is
- // just calling the parent.
- // This also helps to work around for https://github.com/Microsoft/TypeScript/issues/12439
- // that sets 'design:paramtypes' to []
- // if a class inherits from another class but has no ctor declared itself.
- if (isDelegateCtor(typeStr)) {
- return null;
- }
- // Prefer the direct API.
- if (type.parameters && type.parameters !== parentCtor.parameters) {
- return type.parameters;
- }
- // API of tsickle for lowering decorators to properties on the class.
- const tsickleCtorParams = type.ctorParameters;
- if (tsickleCtorParams && tsickleCtorParams !== parentCtor.ctorParameters) {
- // Newer tsickle uses a function closure
- // Retain the non-function case for compatibility with older tsickle
- const ctorParameters = typeof tsickleCtorParams === 'function' ? tsickleCtorParams() : tsickleCtorParams;
- const paramTypes = ctorParameters.map((ctorParam) => ctorParam && ctorParam.type);
- const paramAnnotations = ctorParameters.map((ctorParam) => ctorParam && convertTsickleDecoratorIntoMetadata(ctorParam.decorators));
- return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
- }
- // API for metadata created by invoking the decorators.
- const paramAnnotations = type.hasOwnProperty(PARAMETERS) && type[PARAMETERS];
- const paramTypes = this._reflect && this._reflect.getOwnMetadata &&
- this._reflect.getOwnMetadata('design:paramtypes', type);
- if (paramTypes || paramAnnotations) {
- return this._zipTypesAndAnnotations(paramTypes, paramAnnotations);
- }
- // If a class has no decorators, at least create metadata
- // based on function.length.
- // Note: We know that this is a real constructor as we checked
- // the content of the constructor above.
- return newArray(type.length);
- }
- parameters(type) {
- // Note: only report metadata if we have at least one class decorator
- // to stay in sync with the static reflector.
- if (!isType(type)) {
- return [];
- }
- const parentCtor = getParentCtor(type);
- let parameters = this._ownParameters(type, parentCtor);
- if (!parameters && parentCtor !== Object) {
- parameters = this.parameters(parentCtor);
- }
- return parameters || [];
- }
- _ownAnnotations(typeOrFunc, parentCtor) {
- // Prefer the direct API.
- if (typeOrFunc.annotations && typeOrFunc.annotations !== parentCtor.annotations) {
- let annotations = typeOrFunc.annotations;
- if (typeof annotations === 'function' && annotations.annotations) {
- annotations = annotations.annotations;
- }
- return annotations;
- }
- // API of tsickle for lowering decorators to properties on the class.
- if (typeOrFunc.decorators && typeOrFunc.decorators !== parentCtor.decorators) {
- return convertTsickleDecoratorIntoMetadata(typeOrFunc.decorators);
- }
- // API for metadata created by invoking the decorators.
- if (typeOrFunc.hasOwnProperty(ANNOTATIONS)) {
- return typeOrFunc[ANNOTATIONS];
- }
- return null;
- }
- annotations(typeOrFunc) {
- if (!isType(typeOrFunc)) {
- return [];
- }
- const parentCtor = getParentCtor(typeOrFunc);
- const ownAnnotations = this._ownAnnotations(typeOrFunc, parentCtor) || [];
- const parentAnnotations = parentCtor !== Object ? this.annotations(parentCtor) : [];
- return parentAnnotations.concat(ownAnnotations);
- }
- _ownPropMetadata(typeOrFunc, parentCtor) {
- // Prefer the direct API.
- if (typeOrFunc.propMetadata &&
- typeOrFunc.propMetadata !== parentCtor.propMetadata) {
- let propMetadata = typeOrFunc.propMetadata;
- if (typeof propMetadata === 'function' && propMetadata.propMetadata) {
- propMetadata = propMetadata.propMetadata;
- }
- return propMetadata;
- }
- // API of tsickle for lowering decorators to properties on the class.
- if (typeOrFunc.propDecorators &&
- typeOrFunc.propDecorators !== parentCtor.propDecorators) {
- const propDecorators = typeOrFunc.propDecorators;
- const propMetadata = {};
- Object.keys(propDecorators).forEach(prop => {
- propMetadata[prop] = convertTsickleDecoratorIntoMetadata(propDecorators[prop]);
- });
- return propMetadata;
- }
- // API for metadata created by invoking the decorators.
- if (typeOrFunc.hasOwnProperty(PROP_METADATA)) {
- return typeOrFunc[PROP_METADATA];
- }
- return null;
- }
- propMetadata(typeOrFunc) {
- if (!isType(typeOrFunc)) {
- return {};
- }
- const parentCtor = getParentCtor(typeOrFunc);
- const propMetadata = {};
- if (parentCtor !== Object) {
- const parentPropMetadata = this.propMetadata(parentCtor);
- Object.keys(parentPropMetadata).forEach((propName) => {
- propMetadata[propName] = parentPropMetadata[propName];
- });
- }
- const ownPropMetadata = this._ownPropMetadata(typeOrFunc, parentCtor);
- if (ownPropMetadata) {
- Object.keys(ownPropMetadata).forEach((propName) => {
- const decorators = [];
- if (propMetadata.hasOwnProperty(propName)) {
- decorators.push(...propMetadata[propName]);
- }
- decorators.push(...ownPropMetadata[propName]);
- propMetadata[propName] = decorators;
- });
- }
- return propMetadata;
- }
- ownPropMetadata(typeOrFunc) {
- if (!isType(typeOrFunc)) {
- return {};
- }
- return this._ownPropMetadata(typeOrFunc, getParentCtor(typeOrFunc)) || {};
- }
- hasLifecycleHook(type, lcProperty) {
- return type instanceof Type && lcProperty in type.prototype;
- }
- }
- function convertTsickleDecoratorIntoMetadata(decoratorInvocations) {
- if (!decoratorInvocations) {
- return [];
- }
- return decoratorInvocations.map(decoratorInvocation => {
- const decoratorType = decoratorInvocation.type;
- const annotationCls = decoratorType.annotationCls;
- const annotationArgs = decoratorInvocation.args ? decoratorInvocation.args : [];
- return new annotationCls(...annotationArgs);
- });
- }
- function getParentCtor(ctor) {
- const parentProto = ctor.prototype ? Object.getPrototypeOf(ctor.prototype) : null;
- const parentCtor = parentProto ? parentProto.constructor : null;
- // Note: We always use `Object` as the null value
- // to simplify checking later on.
- return parentCtor || Object;
- }
- function ngDevModeResetPerfCounters() {
- const locationString = typeof location !== 'undefined' ? location.toString() : '';
- const newCounters = {
- namedConstructors: locationString.indexOf('ngDevMode=namedConstructors') != -1,
- firstCreatePass: 0,
- tNode: 0,
- tView: 0,
- rendererCreateTextNode: 0,
- rendererSetText: 0,
- rendererCreateElement: 0,
- rendererAddEventListener: 0,
- rendererSetAttribute: 0,
- rendererRemoveAttribute: 0,
- rendererSetProperty: 0,
- rendererSetClassName: 0,
- rendererAddClass: 0,
- rendererRemoveClass: 0,
- rendererSetStyle: 0,
- rendererRemoveStyle: 0,
- rendererDestroy: 0,
- rendererDestroyNode: 0,
- rendererMoveNode: 0,
- rendererRemoveNode: 0,
- rendererAppendChild: 0,
- rendererInsertBefore: 0,
- rendererCreateComment: 0,
- hydratedNodes: 0,
- hydratedComponents: 0,
- dehydratedViewsRemoved: 0,
- dehydratedViewsCleanupRuns: 0,
- componentsSkippedHydration: 0,
- };
- // Make sure to refer to ngDevMode as ['ngDevMode'] for closure.
- const allowNgDevModeTrue = locationString.indexOf('ngDevMode=false') === -1;
- _global['ngDevMode'] = allowNgDevModeTrue && newCounters;
- return newCounters;
- }
- /**
- * This function checks to see if the `ngDevMode` has been set. If yes,
- * then we honor it, otherwise we default to dev mode with additional checks.
- *
- * The idea is that unless we are doing production build where we explicitly
- * set `ngDevMode == false` we should be helping the developer by providing
- * as much early warning and errors as possible.
- *
- * `ɵɵdefineComponent` is guaranteed to have been called before any component template functions
- * (and thus Ivy instructions), so a single initialization there is sufficient to ensure ngDevMode
- * is defined for the entire instruction set.
- *
- * When checking `ngDevMode` on toplevel, always init it before referencing it
- * (e.g. `((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode())`), otherwise you can
- * get a `ReferenceError` like in https://github.com/angular/angular/issues/31595.
- *
- * Details on possible values for `ngDevMode` can be found on its docstring.
- *
- * NOTE:
- * - changes to the `ngDevMode` name must be synced with `compiler-cli/src/tooling.ts`.
- */
- function initNgDevMode() {
- // The below checks are to ensure that calling `initNgDevMode` multiple times does not
- // reset the counters.
- // If the `ngDevMode` is not an object, then it means we have not created the perf counters
- // yet.
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- if (typeof ngDevMode !== 'object') {
- ngDevModeResetPerfCounters();
- }
- return typeof ngDevMode !== 'undefined' && !!ngDevMode;
- }
- return false;
- }
- let _injectorProfilerContext;
- function getInjectorProfilerContext() {
- !ngDevMode && throwError('getInjectorProfilerContext should never be called in production mode');
- return _injectorProfilerContext;
- }
- function setInjectorProfilerContext(context) {
- !ngDevMode && throwError('setInjectorProfilerContext should never be called in production mode');
- const previous = _injectorProfilerContext;
- _injectorProfilerContext = context;
- return previous;
- }
- let injectorProfilerCallback = null;
- /**
- * Sets the callback function which will be invoked during certain DI events within the
- * runtime (for example: injecting services, creating injectable instances, configuring providers)
- *
- * Warning: this function is *INTERNAL* and should not be relied upon in application's code.
- * The contract of the function might be changed in any release and/or the function can be removed
- * completely.
- *
- * @param profiler function provided by the caller or null value to disable profiling.
- */
- const setInjectorProfiler = (injectorProfiler) => {
- !ngDevMode && throwError('setInjectorProfiler should never be called in production mode');
- injectorProfilerCallback = injectorProfiler;
- };
- /**
- * Injector profiler function which emits on DI events executed by the runtime.
- *
- * @param event InjectorProfilerEvent corresponding to the DI event being emitted
- */
- function injectorProfiler(event) {
- !ngDevMode && throwError('Injector profiler should never be called in production mode');
- if (injectorProfilerCallback != null /* both `null` and `undefined` */) {
- injectorProfilerCallback(event);
- }
- }
- /**
- * Emits an InjectorProfilerEventType.ProviderConfigured to the injector profiler. The data in the
- * emitted event includes the raw provider, as well as the token that provider is providing.
- *
- * @param provider A provider object
- */
- function emitProviderConfiguredEvent(provider, isViewProvider = false) {
- !ngDevMode && throwError('Injector profiler should never be called in production mode');
- injectorProfiler({
- type: 2 /* InjectorProfilerEventType.ProviderConfigured */,
- context: getInjectorProfilerContext(),
- providerRecord: {
- token: typeof provider === 'function' ? provider : resolveForwardRef(provider.provide),
- provider,
- isViewProvider
- }
- });
- }
- /**
- * Emits an event to the injector profiler with the instance that was created. Note that
- * the injector associated with this emission can be accessed by using getDebugInjectContext()
- *
- * @param instance an object created by an injector
- */
- function emitInstanceCreatedByInjectorEvent(instance) {
- !ngDevMode && throwError('Injector profiler should never be called in production mode');
- injectorProfiler({
- type: 1 /* InjectorProfilerEventType.InstanceCreatedByInjector */,
- context: getInjectorProfilerContext(),
- instance: { value: instance }
- });
- }
- /**
- * @param token DI token associated with injected service
- * @param value the instance of the injected service (i.e the result of `inject(token)`)
- * @param flags the flags that the token was injected with
- */
- function emitInjectEvent(token, value, flags) {
- !ngDevMode && throwError('Injector profiler should never be called in production mode');
- injectorProfiler({
- type: 0 /* InjectorProfilerEventType.Inject */,
- context: getInjectorProfilerContext(),
- service: { token, value, flags }
- });
- }
- function runInInjectorProfilerContext(injector, token, callback) {
- !ngDevMode &&
- throwError('runInInjectorProfilerContext should never be called in production mode');
- const prevInjectContext = setInjectorProfilerContext({ injector, token });
- try {
- callback();
- }
- finally {
- setInjectorProfilerContext(prevInjectContext);
- }
- }
- function isEnvironmentProviders(value) {
- return value && !!value.ɵproviders;
- }
- /**
- * Used for stringify render output in Ivy.
- * Important! This function is very performance-sensitive and we should
- * be extra careful not to introduce megamorphic reads in it.
- * Check `core/test/render3/perf/render_stringify` for benchmarks and alternate implementations.
- */
- function renderStringify(value) {
- if (typeof value === 'string')
- return value;
- if (value == null)
- return '';
- // Use `String` so that it invokes the `toString` method of the value. Note that this
- // appears to be faster than calling `value.toString` (see `render_stringify` benchmark).
- return String(value);
- }
- /**
- * Used to stringify a value so that it can be displayed in an error message.
- * Important! This function contains a megamorphic read and should only be
- * used for error messages.
- */
- function stringifyForError(value) {
- if (typeof value === 'function')
- return value.name || value.toString();
- if (typeof value === 'object' && value != null && typeof value.type === 'function') {
- return value.type.name || value.type.toString();
- }
- return renderStringify(value);
- }
- /** Called when directives inject each other (creating a circular dependency) */
- function throwCyclicDependencyError(token, path) {
- const depPath = path ? `. Dependency path: ${path.join(' > ')} > ${token}` : '';
- throw new RuntimeError(-200 /* RuntimeErrorCode.CYCLIC_DI_DEPENDENCY */, `Circular dependency in DI detected for ${token}${depPath}`);
- }
- function throwMixedMultiProviderError() {
- throw new Error(`Cannot mix multi providers and regular providers`);
- }
- function throwInvalidProviderError(ngModuleType, providers, provider) {
- if (ngModuleType && providers) {
- const providerDetail = providers.map(v => v == provider ? '?' + provider + '?' : '...');
- throw new Error(`Invalid provider for the NgModule '${stringify(ngModuleType)}' - only instances of Provider and Type are allowed, got: [${providerDetail.join(', ')}]`);
- }
- else if (isEnvironmentProviders(provider)) {
- if (provider.ɵfromNgModule) {
- throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, `Invalid providers from 'importProvidersFrom' present in a non-environment injector. 'importProvidersFrom' can't be used for component providers.`);
- }
- else {
- throw new RuntimeError(207 /* RuntimeErrorCode.PROVIDER_IN_WRONG_CONTEXT */, `Invalid providers present in a non-environment injector. 'EnvironmentProviders' can't be used for component providers.`);
- }
- }
- else {
- throw new Error('Invalid provider');
- }
- }
- /** Throws an error when a token is not found in DI. */
- function throwProviderNotFoundError(token, injectorName) {
- const injectorDetails = injectorName ? ` in ${injectorName}` : '';
- throw new RuntimeError(-201 /* RuntimeErrorCode.PROVIDER_NOT_FOUND */, ngDevMode && `No provider for ${stringifyForError(token)} found${injectorDetails}`);
- }
- /**
- * Injection flags for DI.
- *
- * @publicApi
- * @deprecated use an options object for [`inject`](api/core/inject) instead.
- */
- var InjectFlags;
- (function (InjectFlags) {
- // TODO(alxhub): make this 'const' (and remove `InternalInjectFlags` enum) when ngc no longer
- // writes exports of it into ngfactory files.
- /** Check self and check parent injector if needed */
- InjectFlags[InjectFlags["Default"] = 0] = "Default";
- /**
- * Specifies that an injector should retrieve a dependency from any injector until reaching the
- * host element of the current component. (Only used with Element Injector)
- */
- InjectFlags[InjectFlags["Host"] = 1] = "Host";
- /** Don't ascend to ancestors of the node requesting injection. */
- InjectFlags[InjectFlags["Self"] = 2] = "Self";
- /** Skip the node that is requesting injection. */
- InjectFlags[InjectFlags["SkipSelf"] = 4] = "SkipSelf";
- /** Inject `defaultValue` instead if token not found. */
- InjectFlags[InjectFlags["Optional"] = 8] = "Optional";
- })(InjectFlags || (InjectFlags = {}));
- /**
- * Current implementation of inject.
- *
- * By default, it is `injectInjectorOnly`, which makes it `Injector`-only aware. It can be changed
- * to `directiveInject`, which brings in the `NodeInjector` system of ivy. It is designed this
- * way for two reasons:
- * 1. `Injector` should not depend on ivy logic.
- * 2. To maintain tree shake-ability we don't want to bring in unnecessary code.
- */
- let _injectImplementation;
- function getInjectImplementation() {
- return _injectImplementation;
- }
- /**
- * Sets the current inject implementation.
- */
- function setInjectImplementation(impl) {
- const previous = _injectImplementation;
- _injectImplementation = impl;
- return previous;
- }
- /**
- * Injects `root` tokens in limp mode.
- *
- * If no injector exists, we can still inject tree-shakable providers which have `providedIn` set to
- * `"root"`. This is known as the limp mode injection. In such case the value is stored in the
- * injectable definition.
- */
- function injectRootLimpMode(token, notFoundValue, flags) {
- const injectableDef = getInjectableDef(token);
- if (injectableDef && injectableDef.providedIn == 'root') {
- return injectableDef.value === undefined ? injectableDef.value = injectableDef.factory() :
- injectableDef.value;
- }
- if (flags & InjectFlags.Optional)
- return null;
- if (notFoundValue !== undefined)
- return notFoundValue;
- throwProviderNotFoundError(stringify(token), 'Injector');
- }
- /**
- * Assert that `_injectImplementation` is not `fn`.
- *
- * This is useful, to prevent infinite recursion.
- *
- * @param fn Function which it should not equal to
- */
- function assertInjectImplementationNotEqual(fn) {
- ngDevMode &&
- assertNotEqual(_injectImplementation, fn, 'Calling ɵɵinject would cause infinite recursion');
- }
- const _THROW_IF_NOT_FOUND = {};
- const THROW_IF_NOT_FOUND = _THROW_IF_NOT_FOUND;
- /*
- * Name of a property (that we patch onto DI decorator), which is used as an annotation of which
- * InjectFlag this decorator represents. This allows to avoid direct references to the DI decorators
- * in the code, thus making them tree-shakable.
- */
- const DI_DECORATOR_FLAG = '__NG_DI_FLAG__';
- const NG_TEMP_TOKEN_PATH = 'ngTempTokenPath';
- const NG_TOKEN_PATH = 'ngTokenPath';
- const NEW_LINE = /\n/gm;
- const NO_NEW_LINE = 'ɵ';
- const SOURCE = '__source';
- /**
- * Current injector value used by `inject`.
- * - `undefined`: it is an error to call `inject`
- * - `null`: `inject` can be called but there is no injector (limp-mode).
- * - Injector instance: Use the injector for resolution.
- */
- let _currentInjector = undefined;
- function getCurrentInjector() {
- return _currentInjector;
- }
- function setCurrentInjector(injector) {
- const former = _currentInjector;
- _currentInjector = injector;
- return former;
- }
- function injectInjectorOnly(token, flags = InjectFlags.Default) {
- if (_currentInjector === undefined) {
- throw new RuntimeError(-203 /* RuntimeErrorCode.MISSING_INJECTION_CONTEXT */, ngDevMode &&
- `inject() must be called from an injection context such as a constructor, a factory function, a field initializer, or a function used with \`runInInjectionContext\`.`);
- }
- else if (_currentInjector === null) {
- return injectRootLimpMode(token, undefined, flags);
- }
- else {
- const value = _currentInjector.get(token, flags & InjectFlags.Optional ? null : undefined, flags);
- ngDevMode && emitInjectEvent(token, value, flags);
- return value;
- }
- }
- function ɵɵinject(token, flags = InjectFlags.Default) {
- return (getInjectImplementation() || injectInjectorOnly)(resolveForwardRef(token), flags);
- }
- /**
- * Throws an error indicating that a factory function could not be generated by the compiler for a
- * particular class.
- *
- * The name of the class is not mentioned here, but will be in the generated factory function name
- * and thus in the stack trace.
- *
- * @codeGenApi
- */
- function ɵɵinvalidFactoryDep(index) {
- throw new RuntimeError(202 /* RuntimeErrorCode.INVALID_FACTORY_DEPENDENCY */, ngDevMode &&
- `This constructor is not compatible with Angular Dependency Injection because its dependency at index ${index} of the parameter list is invalid.
- This can happen if the dependency type is a primitive like a string or if an ancestor of this class is missing an Angular decorator.
- Please check that 1) the type for the parameter at index ${index} is correct and 2) the correct Angular decorators are defined for this class and its ancestors.`);
- }
- /**
- * Injects a token from the currently active injector.
- * `inject` is only supported in an [injection context](/guide/dependency-injection-context). It can
- * be used during:
- * - Construction (via the `constructor`) of a class being instantiated by the DI system, such
- * as an `@Injectable` or `@Component`.
- * - In the initializer for fields of such classes.
- * - In the factory function specified for `useFactory` of a `Provider` or an `@Injectable`.
- * - In the `factory` function specified for an `InjectionToken`.
- * - In a stackframe of a function call in a DI context
- *
- * @param token A token that represents a dependency that should be injected.
- * @param flags Optional flags that control how injection is executed.
- * The flags correspond to injection strategies that can be specified with
- * parameter decorators `@Host`, `@Self`, `@SkipSelf`, and `@Optional`.
- * @returns the injected value if operation is successful, `null` otherwise.
- * @throws if called outside of a supported context.
- *
- * @usageNotes
- * In practice the `inject()` calls are allowed in a constructor, a constructor parameter and a
- * field initializer:
- *
- * ```typescript
- * @Injectable({providedIn: 'root'})
- * export class Car {
- * radio: Radio|undefined;
- * // OK: field initializer
- * spareTyre = inject(Tyre);
- *
- * constructor() {
- * // OK: constructor body
- * this.radio = inject(Radio);
- * }
- * }
- * ```
- *
- * It is also legal to call `inject` from a provider's factory:
- *
- * ```typescript
- * providers: [
- * {provide: Car, useFactory: () => {
- * // OK: a class factory
- * const engine = inject(Engine);
- * return new Car(engine);
- * }}
- * ]
- * ```
- *
- * Calls to the `inject()` function outside of the class creation context will result in error. Most
- * notably, calls to `inject()` are disallowed after a class instance was created, in methods
- * (including lifecycle hooks):
- *
- * ```typescript
- * @Component({ ... })
- * export class CarComponent {
- * ngOnInit() {
- * // ERROR: too late, the component instance was already created
- * const engine = inject(Engine);
- * engine.start();
- * }
- * }
- * ```
- *
- * @publicApi
- */
- function inject$1(token, flags = InjectFlags.Default) {
- return ɵɵinject(token, convertToBitFlags(flags));
- }
- // Converts object-based DI flags (`InjectOptions`) to bit flags (`InjectFlags`).
- function convertToBitFlags(flags) {
- if (typeof flags === 'undefined' || typeof flags === 'number') {
- return flags;
- }
- // While TypeScript doesn't accept it without a cast, bitwise OR with false-y values in
- // JavaScript is a no-op. We can use that for a very codesize-efficient conversion from
- // `InjectOptions` to `InjectFlags`.
- return (0 /* InternalInjectFlags.Default */ | // comment to force a line break in the formatter
- (flags.optional && 8 /* InternalInjectFlags.Optional */) |
- (flags.host && 1 /* InternalInjectFlags.Host */) |
- (flags.self && 2 /* InternalInjectFlags.Self */) |
- (flags.skipSelf && 4 /* InternalInjectFlags.SkipSelf */));
- }
- function injectArgs(types) {
- const args = [];
- for (let i = 0; i < types.length; i++) {
- const arg = resolveForwardRef(types[i]);
- if (Array.isArray(arg)) {
- if (arg.length === 0) {
- throw new RuntimeError(900 /* RuntimeErrorCode.INVALID_DIFFER_INPUT */, ngDevMode && 'Arguments array must have arguments.');
- }
- let type = undefined;
- let flags = InjectFlags.Default;
- for (let j = 0; j < arg.length; j++) {
- const meta = arg[j];
- const flag = getInjectFlag(meta);
- if (typeof flag === 'number') {
- // Special case when we handle @Inject decorator.
- if (flag === -1 /* DecoratorFlags.Inject */) {
- type = meta.token;
- }
- else {
- flags |= flag;
- }
- }
- else {
- type = meta;
- }
- }
- args.push(ɵɵinject(type, flags));
- }
- else {
- args.push(ɵɵinject(arg));
- }
- }
- return args;
- }
- /**
- * Attaches a given InjectFlag to a given decorator using monkey-patching.
- * Since DI decorators can be used in providers `deps` array (when provider is configured using
- * `useFactory`) without initialization (e.g. `Host`) and as an instance (e.g. `new Host()`), we
- * attach the flag to make it available both as a static property and as a field on decorator
- * instance.
- *
- * @param decorator Provided DI decorator.
- * @param flag InjectFlag that should be applied.
- */
- function attachInjectFlag(decorator, flag) {
- decorator[DI_DECORATOR_FLAG] = flag;
- decorator.prototype[DI_DECORATOR_FLAG] = flag;
- return decorator;
- }
- /**
- * Reads monkey-patched property that contains InjectFlag attached to a decorator.
- *
- * @param token Token that may contain monkey-patched DI flags property.
- */
- function getInjectFlag(token) {
- return token[DI_DECORATOR_FLAG];
- }
- function catchInjectorError(e, token, injectorErrorName, source) {
- const tokenPath = e[NG_TEMP_TOKEN_PATH];
- if (token[SOURCE]) {
- tokenPath.unshift(token[SOURCE]);
- }
- e.message = formatError('\n' + e.message, tokenPath, injectorErrorName, source);
- e[NG_TOKEN_PATH] = tokenPath;
- e[NG_TEMP_TOKEN_PATH] = null;
- throw e;
- }
- function formatError(text, obj, injectorErrorName, source = null) {
- text = text && text.charAt(0) === '\n' && text.charAt(1) == NO_NEW_LINE ? text.slice(2) : text;
- let context = stringify(obj);
- if (Array.isArray(obj)) {
- context = obj.map(stringify).join(' -> ');
- }
- else if (typeof obj === 'object') {
- let parts = [];
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- let value = obj[key];
- parts.push(key + ':' + (typeof value === 'string' ? JSON.stringify(value) : stringify(value)));
- }
- }
- context = `{${parts.join(', ')}}`;
- }
- return `${injectorErrorName}${source ? '(' + source + ')' : ''}[${context}]: ${text.replace(NEW_LINE, '\n ')}`;
- }
- /**
- * Inject decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const Inject = attachInjectFlag(
- // Disable tslint because `DecoratorFlags` is a const enum which gets inlined.
- // tslint:disable-next-line: no-toplevel-property-access
- makeParamDecorator('Inject', (token) => ({ token })), -1 /* DecoratorFlags.Inject */);
- /**
- * Optional decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const Optional =
- // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
- // tslint:disable-next-line: no-toplevel-property-access
- attachInjectFlag(makeParamDecorator('Optional'), 8 /* InternalInjectFlags.Optional */);
- /**
- * Self decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const Self =
- // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
- // tslint:disable-next-line: no-toplevel-property-access
- attachInjectFlag(makeParamDecorator('Self'), 2 /* InternalInjectFlags.Self */);
- /**
- * `SkipSelf` decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const SkipSelf =
- // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
- // tslint:disable-next-line: no-toplevel-property-access
- attachInjectFlag(makeParamDecorator('SkipSelf'), 4 /* InternalInjectFlags.SkipSelf */);
- /**
- * Host decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const Host =
- // Disable tslint because `InternalInjectFlags` is a const enum which gets inlined.
- // tslint:disable-next-line: no-toplevel-property-access
- attachInjectFlag(makeParamDecorator('Host'), 1 /* InternalInjectFlags.Host */);
- /**
- * The strategy that the default change detector uses to detect changes.
- * When set, takes effect the next time change detection is triggered.
- *
- * @see {@link ChangeDetectorRef#usage-notes Change detection usage}
- *
- * @publicApi
- */
- var ChangeDetectionStrategy;
- (function (ChangeDetectionStrategy) {
- /**
- * Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated
- * until reactivated by setting the strategy to `Default` (`CheckAlways`).
- * Change detection can still be explicitly invoked.
- * This strategy applies to all child directives and cannot be overridden.
- */
- ChangeDetectionStrategy[ChangeDetectionStrategy["OnPush"] = 0] = "OnPush";
- /**
- * Use the default `CheckAlways` strategy, in which change detection is automatic until
- * explicitly deactivated.
- */
- ChangeDetectionStrategy[ChangeDetectionStrategy["Default"] = 1] = "Default";
- })(ChangeDetectionStrategy || (ChangeDetectionStrategy = {}));
- /**
- * Defines the CSS styles encapsulation policies for the {@link Component} decorator's
- * `encapsulation` option.
- *
- * See {@link Component#encapsulation encapsulation}.
- *
- * @usageNotes
- * ### Example
- *
- * {@example core/ts/metadata/encapsulation.ts region='longform'}
- *
- * @publicApi
- */
- var ViewEncapsulation;
- (function (ViewEncapsulation) {
- // TODO: consider making `ViewEncapsulation` a `const enum` instead. See
- // https://github.com/angular/angular/issues/44119 for additional information.
- /**
- * Emulates a native Shadow DOM encapsulation behavior by adding a specific attribute to the
- * component's host element and applying the same attribute to all the CSS selectors provided
- * via {@link Component#styles styles} or {@link Component#styleUrls styleUrls}.
- *
- * This is the default option.
- */
- ViewEncapsulation[ViewEncapsulation["Emulated"] = 0] = "Emulated";
- // Historically the 1 value was for `Native` encapsulation which has been removed as of v11.
- /**
- * Doesn't provide any sort of CSS style encapsulation, meaning that all the styles provided
- * via {@link Component#styles styles} or {@link Component#styleUrls styleUrls} are applicable
- * to any HTML element of the application regardless of their host Component.
- */
- ViewEncapsulation[ViewEncapsulation["None"] = 2] = "None";
- /**
- * Uses the browser's native Shadow DOM API to encapsulate CSS styles, meaning that it creates
- * a ShadowRoot for the component's host element which is then used to encapsulate
- * all the Component's styling.
- */
- ViewEncapsulation[ViewEncapsulation["ShadowDom"] = 3] = "ShadowDom";
- })(ViewEncapsulation || (ViewEncapsulation = {}));
- /**
- * This file contains reuseable "empty" symbols that can be used as default return values
- * in different parts of the rendering code. Because the same symbols are returned, this
- * allows for identity checks against these values to be consistently used by the framework
- * code.
- */
- const EMPTY_OBJ = {};
- const EMPTY_ARRAY = [];
- // freezing the values prevents any code from accidentally inserting new values in
- if ((typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode()) {
- // These property accesses can be ignored because ngDevMode will be set to false
- // when optimizing code and the whole if statement will be dropped.
- // tslint:disable-next-line:no-toplevel-property-access
- Object.freeze(EMPTY_OBJ);
- // tslint:disable-next-line:no-toplevel-property-access
- Object.freeze(EMPTY_ARRAY);
- }
- const NG_COMP_DEF = getClosureSafeProperty({ ɵcmp: getClosureSafeProperty });
- const NG_DIR_DEF = getClosureSafeProperty({ ɵdir: getClosureSafeProperty });
- const NG_PIPE_DEF = getClosureSafeProperty({ ɵpipe: getClosureSafeProperty });
- const NG_MOD_DEF = getClosureSafeProperty({ ɵmod: getClosureSafeProperty });
- const NG_FACTORY_DEF = getClosureSafeProperty({ ɵfac: getClosureSafeProperty });
- /**
- * If a directive is diPublic, bloomAdd sets a property on the type with this constant as
- * the key and the directive's unique ID as the value. This allows us to map directives to their
- * bloom filter bit for DI.
- */
- // TODO(misko): This is wrong. The NG_ELEMENT_ID should never be minified.
- const NG_ELEMENT_ID = getClosureSafeProperty({ __NG_ELEMENT_ID__: getClosureSafeProperty });
- /**
- * The `NG_ENV_ID` field on a DI token indicates special processing in the `EnvironmentInjector`:
- * getting such tokens from the `EnvironmentInjector` will bypass the standard DI resolution
- * strategy and instead will return implementation produced by the `NG_ENV_ID` factory function.
- *
- * This particular retrieval of DI tokens is mostly done to eliminate circular dependencies and
- * improve tree-shaking.
- */
- const NG_ENV_ID = getClosureSafeProperty({ __NG_ENV_ID__: getClosureSafeProperty });
- /**
- * Returns an index of `classToSearch` in `className` taking token boundaries into account.
- *
- * `classIndexOf('AB A', 'A', 0)` will be 3 (not 0 since `AB!==A`)
- *
- * @param className A string containing classes (whitespace separated)
- * @param classToSearch A class name to locate
- * @param startingIndex Starting location of search
- * @returns an index of the located class (or -1 if not found)
- */
- function classIndexOf(className, classToSearch, startingIndex) {
- ngDevMode && assertNotEqual(classToSearch, '', 'can not look for "" string.');
- let end = className.length;
- while (true) {
- const foundIndex = className.indexOf(classToSearch, startingIndex);
- if (foundIndex === -1)
- return foundIndex;
- if (foundIndex === 0 || className.charCodeAt(foundIndex - 1) <= 32 /* CharCode.SPACE */) {
- // Ensure that it has leading whitespace
- const length = classToSearch.length;
- if (foundIndex + length === end ||
- className.charCodeAt(foundIndex + length) <= 32 /* CharCode.SPACE */) {
- // Ensure that it has trailing whitespace
- return foundIndex;
- }
- }
- // False positive, keep searching from where we left off.
- startingIndex = foundIndex + 1;
- }
- }
- /**
- * Assigns all attribute values to the provided element via the inferred renderer.
- *
- * This function accepts two forms of attribute entries:
- *
- * default: (key, value):
- * attrs = [key1, value1, key2, value2]
- *
- * namespaced: (NAMESPACE_MARKER, uri, name, value)
- * attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
- *
- * The `attrs` array can contain a mix of both the default and namespaced entries.
- * The "default" values are set without a marker, but if the function comes across
- * a marker value then it will attempt to set a namespaced value. If the marker is
- * not of a namespaced value then the function will quit and return the index value
- * where it stopped during the iteration of the attrs array.
- *
- * See [AttributeMarker] to understand what the namespace marker value is.
- *
- * Note that this instruction does not support assigning style and class values to
- * an element. See `elementStart` and `elementHostAttrs` to learn how styling values
- * are applied to an element.
- * @param renderer The renderer to be used
- * @param native The element that the attributes will be assigned to
- * @param attrs The attribute array of values that will be assigned to the element
- * @returns the index value that was last accessed in the attributes array
- */
- function setUpAttributes(renderer, native, attrs) {
- let i = 0;
- while (i < attrs.length) {
- const value = attrs[i];
- if (typeof value === 'number') {
- // only namespaces are supported. Other value types (such as style/class
- // entries) are not supported in this function.
- if (value !== 0 /* AttributeMarker.NamespaceURI */) {
- break;
- }
- // we just landed on the marker value ... therefore
- // we should skip to the next entry
- i++;
- const namespaceURI = attrs[i++];
- const attrName = attrs[i++];
- const attrVal = attrs[i++];
- ngDevMode && ngDevMode.rendererSetAttribute++;
- renderer.setAttribute(native, attrName, attrVal, namespaceURI);
- }
- else {
- // attrName is string;
- const attrName = value;
- const attrVal = attrs[++i];
- // Standard attributes
- ngDevMode && ngDevMode.rendererSetAttribute++;
- if (isAnimationProp(attrName)) {
- renderer.setProperty(native, attrName, attrVal);
- }
- else {
- renderer.setAttribute(native, attrName, attrVal);
- }
- i++;
- }
- }
- // another piece of code may iterate over the same attributes array. Therefore
- // it may be helpful to return the exact spot where the attributes array exited
- // whether by running into an unsupported marker or if all the static values were
- // iterated over.
- return i;
- }
- /**
- * Test whether the given value is a marker that indicates that the following
- * attribute values in a `TAttributes` array are only the names of attributes,
- * and not name-value pairs.
- * @param marker The attribute marker to test.
- * @returns true if the marker is a "name-only" marker (e.g. `Bindings`, `Template` or `I18n`).
- */
- function isNameOnlyAttributeMarker(marker) {
- return marker === 3 /* AttributeMarker.Bindings */ || marker === 4 /* AttributeMarker.Template */ ||
- marker === 6 /* AttributeMarker.I18n */;
- }
- function isAnimationProp(name) {
- // Perf note: accessing charCodeAt to check for the first character of a string is faster as
- // compared to accessing a character at index 0 (ex. name[0]). The main reason for this is that
- // charCodeAt doesn't allocate memory to return a substring.
- return name.charCodeAt(0) === 64 /* CharCode.AT_SIGN */;
- }
- /**
- * Merges `src` `TAttributes` into `dst` `TAttributes` removing any duplicates in the process.
- *
- * This merge function keeps the order of attrs same.
- *
- * @param dst Location of where the merged `TAttributes` should end up.
- * @param src `TAttributes` which should be appended to `dst`
- */
- function mergeHostAttrs(dst, src) {
- if (src === null || src.length === 0) {
- // do nothing
- }
- else if (dst === null || dst.length === 0) {
- // We have source, but dst is empty, just make a copy.
- dst = src.slice();
- }
- else {
- let srcMarker = -1 /* AttributeMarker.ImplicitAttributes */;
- for (let i = 0; i < src.length; i++) {
- const item = src[i];
- if (typeof item === 'number') {
- srcMarker = item;
- }
- else {
- if (srcMarker === 0 /* AttributeMarker.NamespaceURI */) {
- // Case where we need to consume `key1`, `key2`, `value` items.
- }
- else if (srcMarker === -1 /* AttributeMarker.ImplicitAttributes */ ||
- srcMarker === 2 /* AttributeMarker.Styles */) {
- // Case where we have to consume `key1` and `value` only.
- mergeHostAttribute(dst, srcMarker, item, null, src[++i]);
- }
- else {
- // Case where we have to consume `key1` only.
- mergeHostAttribute(dst, srcMarker, item, null, null);
- }
- }
- }
- }
- return dst;
- }
- /**
- * Append `key`/`value` to existing `TAttributes` taking region marker and duplicates into account.
- *
- * @param dst `TAttributes` to append to.
- * @param marker Region where the `key`/`value` should be added.
- * @param key1 Key to add to `TAttributes`
- * @param key2 Key to add to `TAttributes` (in case of `AttributeMarker.NamespaceURI`)
- * @param value Value to add or to overwrite to `TAttributes` Only used if `marker` is not Class.
- */
- function mergeHostAttribute(dst, marker, key1, key2, value) {
- let i = 0;
- // Assume that new markers will be inserted at the end.
- let markerInsertPosition = dst.length;
- // scan until correct type.
- if (marker === -1 /* AttributeMarker.ImplicitAttributes */) {
- markerInsertPosition = -1;
- }
- else {
- while (i < dst.length) {
- const dstValue = dst[i++];
- if (typeof dstValue === 'number') {
- if (dstValue === marker) {
- markerInsertPosition = -1;
- break;
- }
- else if (dstValue > marker) {
- // We need to save this as we want the markers to be inserted in specific order.
- markerInsertPosition = i - 1;
- break;
- }
- }
- }
- }
- // search until you find place of insertion
- while (i < dst.length) {
- const item = dst[i];
- if (typeof item === 'number') {
- // since `i` started as the index after the marker, we did not find it if we are at the next
- // marker
- break;
- }
- else if (item === key1) {
- // We already have same token
- if (key2 === null) {
- if (value !== null) {
- dst[i + 1] = value;
- }
- return;
- }
- else if (key2 === dst[i + 1]) {
- dst[i + 2] = value;
- return;
- }
- }
- // Increment counter.
- i++;
- if (key2 !== null)
- i++;
- if (value !== null)
- i++;
- }
- // insert at location.
- if (markerInsertPosition !== -1) {
- dst.splice(markerInsertPosition, 0, marker);
- i = markerInsertPosition + 1;
- }
- dst.splice(i++, 0, key1);
- if (key2 !== null) {
- dst.splice(i++, 0, key2);
- }
- if (value !== null) {
- dst.splice(i++, 0, value);
- }
- }
- const NG_TEMPLATE_SELECTOR = 'ng-template';
- /**
- * Search the `TAttributes` to see if it contains `cssClassToMatch` (case insensitive)
- *
- * @param attrs `TAttributes` to search through.
- * @param cssClassToMatch class to match (lowercase)
- * @param isProjectionMode Whether or not class matching should look into the attribute `class` in
- * addition to the `AttributeMarker.Classes`.
- */
- function isCssClassMatching(attrs, cssClassToMatch, isProjectionMode) {
- // TODO(misko): The fact that this function needs to know about `isProjectionMode` seems suspect.
- // It is strange to me that sometimes the class information comes in form of `class` attribute
- // and sometimes in form of `AttributeMarker.Classes`. Some investigation is needed to determine
- // if that is the right behavior.
- ngDevMode &&
- assertEqual(cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.');
- let i = 0;
- // Indicates whether we are processing value from the implicit
- // attribute section (i.e. before the first marker in the array).
- let isImplicitAttrsSection = true;
- while (i < attrs.length) {
- let item = attrs[i++];
- if (typeof item === 'string' && isImplicitAttrsSection) {
- const value = attrs[i++];
- if (isProjectionMode && item === 'class') {
- // We found a `class` attribute in the implicit attribute section,
- // check if it matches the value of the `cssClassToMatch` argument.
- if (classIndexOf(value.toLowerCase(), cssClassToMatch, 0) !== -1) {
- return true;
- }
- }
- }
- else if (item === 1 /* AttributeMarker.Classes */) {
- // We found the classes section. Start searching for the class.
- while (i < attrs.length && typeof (item = attrs[i++]) == 'string') {
- // while we have strings
- if (item.toLowerCase() === cssClassToMatch)
- return true;
- }
- return false;
- }
- else if (typeof item === 'number') {
- // We've came across a first marker, which indicates
- // that the implicit attribute section is over.
- isImplicitAttrsSection = false;
- }
- }
- return false;
- }
- /**
- * Checks whether the `tNode` represents an inline template (e.g. `*ngFor`).
- *
- * @param tNode current TNode
- */
- function isInlineTemplate(tNode) {
- return tNode.type === 4 /* TNodeType.Container */ && tNode.value !== NG_TEMPLATE_SELECTOR;
- }
- /**
- * Function that checks whether a given tNode matches tag-based selector and has a valid type.
- *
- * Matching can be performed in 2 modes: projection mode (when we project nodes) and regular
- * directive matching mode:
- * - in the "directive matching" mode we do _not_ take TContainer's tagName into account if it is
- * different from NG_TEMPLATE_SELECTOR (value different from NG_TEMPLATE_SELECTOR indicates that a
- * tag name was extracted from * syntax so we would match the same directive twice);
- * - in the "projection" mode, we use a tag name potentially extracted from the * syntax processing
- * (applicable to TNodeType.Container only).
- */
- function hasTagAndTypeMatch(tNode, currentSelector, isProjectionMode) {
- const tagNameToCompare = tNode.type === 4 /* TNodeType.Container */ && !isProjectionMode ? NG_TEMPLATE_SELECTOR : tNode.value;
- return currentSelector === tagNameToCompare;
- }
- /**
- * A utility function to match an Ivy node static data against a simple CSS selector
- *
- * @param node static data of the node to match
- * @param selector The selector to try matching against the node.
- * @param isProjectionMode if `true` we are matching for content projection, otherwise we are doing
- * directive matching.
- * @returns true if node matches the selector.
- */
- function isNodeMatchingSelector(tNode, selector, isProjectionMode) {
- ngDevMode && assertDefined(selector[0], 'Selector should have a tag name');
- let mode = 4 /* SelectorFlags.ELEMENT */;
- const nodeAttrs = tNode.attrs || [];
- // Find the index of first attribute that has no value, only a name.
- const nameOnlyMarkerIdx = getNameOnlyMarkerIndex(nodeAttrs);
- // When processing ":not" selectors, we skip to the next ":not" if the
- // current one doesn't match
- let skipToNextSelector = false;
- for (let i = 0; i < selector.length; i++) {
- const current = selector[i];
- if (typeof current === 'number') {
- // If we finish processing a :not selector and it hasn't failed, return false
- if (!skipToNextSelector && !isPositive(mode) && !isPositive(current)) {
- return false;
- }
- // If we are skipping to the next :not() and this mode flag is positive,
- // it's a part of the current :not() selector, and we should keep skipping
- if (skipToNextSelector && isPositive(current))
- continue;
- skipToNextSelector = false;
- mode = current | (mode & 1 /* SelectorFlags.NOT */);
- continue;
- }
- if (skipToNextSelector)
- continue;
- if (mode & 4 /* SelectorFlags.ELEMENT */) {
- mode = 2 /* SelectorFlags.ATTRIBUTE */ | mode & 1 /* SelectorFlags.NOT */;
- if (current !== '' && !hasTagAndTypeMatch(tNode, current, isProjectionMode) ||
- current === '' && selector.length === 1) {
- if (isPositive(mode))
- return false;
- skipToNextSelector = true;
- }
- }
- else {
- const selectorAttrValue = mode & 8 /* SelectorFlags.CLASS */ ? current : selector[++i];
- // special case for matching against classes when a tNode has been instantiated with
- // class and style values as separate attribute values (e.g. ['title', CLASS, 'foo'])
- if ((mode & 8 /* SelectorFlags.CLASS */) && tNode.attrs !== null) {
- if (!isCssClassMatching(tNode.attrs, selectorAttrValue, isProjectionMode)) {
- if (isPositive(mode))
- return false;
- skipToNextSelector = true;
- }
- continue;
- }
- const attrName = (mode & 8 /* SelectorFlags.CLASS */) ? 'class' : current;
- const attrIndexInNode = findAttrIndexInNode(attrName, nodeAttrs, isInlineTemplate(tNode), isProjectionMode);
- if (attrIndexInNode === -1) {
- if (isPositive(mode))
- return false;
- skipToNextSelector = true;
- continue;
- }
- if (selectorAttrValue !== '') {
- let nodeAttrValue;
- if (attrIndexInNode > nameOnlyMarkerIdx) {
- nodeAttrValue = '';
- }
- else {
- ngDevMode &&
- assertNotEqual(nodeAttrs[attrIndexInNode], 0 /* AttributeMarker.NamespaceURI */, 'We do not match directives on namespaced attributes');
- // we lowercase the attribute value to be able to match
- // selectors without case-sensitivity
- // (selectors are already in lowercase when generated)
- nodeAttrValue = nodeAttrs[attrIndexInNode + 1].toLowerCase();
- }
- const compareAgainstClassName = mode & 8 /* SelectorFlags.CLASS */ ? nodeAttrValue : null;
- if (compareAgainstClassName &&
- classIndexOf(compareAgainstClassName, selectorAttrValue, 0) !== -1 ||
- mode & 2 /* SelectorFlags.ATTRIBUTE */ && selectorAttrValue !== nodeAttrValue) {
- if (isPositive(mode))
- return false;
- skipToNextSelector = true;
- }
- }
- }
- }
- return isPositive(mode) || skipToNextSelector;
- }
- function isPositive(mode) {
- return (mode & 1 /* SelectorFlags.NOT */) === 0;
- }
- /**
- * Examines the attribute's definition array for a node to find the index of the
- * attribute that matches the given `name`.
- *
- * NOTE: This will not match namespaced attributes.
- *
- * Attribute matching depends upon `isInlineTemplate` and `isProjectionMode`.
- * The following table summarizes which types of attributes we attempt to match:
- *
- * ===========================================================================================================
- * Modes | Normal Attributes | Bindings Attributes | Template Attributes | I18n
- * Attributes
- * ===========================================================================================================
- * Inline + Projection | YES | YES | NO | YES
- * -----------------------------------------------------------------------------------------------------------
- * Inline + Directive | NO | NO | YES | NO
- * -----------------------------------------------------------------------------------------------------------
- * Non-inline + Projection | YES | YES | NO | YES
- * -----------------------------------------------------------------------------------------------------------
- * Non-inline + Directive | YES | YES | NO | YES
- * ===========================================================================================================
- *
- * @param name the name of the attribute to find
- * @param attrs the attribute array to examine
- * @param isInlineTemplate true if the node being matched is an inline template (e.g. `*ngFor`)
- * rather than a manually expanded template node (e.g `<ng-template>`).
- * @param isProjectionMode true if we are matching against content projection otherwise we are
- * matching against directives.
- */
- function findAttrIndexInNode(name, attrs, isInlineTemplate, isProjectionMode) {
- if (attrs === null)
- return -1;
- let i = 0;
- if (isProjectionMode || !isInlineTemplate) {
- let bindingsMode = false;
- while (i < attrs.length) {
- const maybeAttrName = attrs[i];
- if (maybeAttrName === name) {
- return i;
- }
- else if (maybeAttrName === 3 /* AttributeMarker.Bindings */ || maybeAttrName === 6 /* AttributeMarker.I18n */) {
- bindingsMode = true;
- }
- else if (maybeAttrName === 1 /* AttributeMarker.Classes */ || maybeAttrName === 2 /* AttributeMarker.Styles */) {
- let value = attrs[++i];
- // We should skip classes here because we have a separate mechanism for
- // matching classes in projection mode.
- while (typeof value === 'string') {
- value = attrs[++i];
- }
- continue;
- }
- else if (maybeAttrName === 4 /* AttributeMarker.Template */) {
- // We do not care about Template attributes in this scenario.
- break;
- }
- else if (maybeAttrName === 0 /* AttributeMarker.NamespaceURI */) {
- // Skip the whole namespaced attribute and value. This is by design.
- i += 4;
- continue;
- }
- // In binding mode there are only names, rather than name-value pairs.
- i += bindingsMode ? 1 : 2;
- }
- // We did not match the attribute
- return -1;
- }
- else {
- return matchTemplateAttribute(attrs, name);
- }
- }
- function isNodeMatchingSelectorList(tNode, selector, isProjectionMode = false) {
- for (let i = 0; i < selector.length; i++) {
- if (isNodeMatchingSelector(tNode, selector[i], isProjectionMode)) {
- return true;
- }
- }
- return false;
- }
- function getProjectAsAttrValue(tNode) {
- const nodeAttrs = tNode.attrs;
- if (nodeAttrs != null) {
- const ngProjectAsAttrIdx = nodeAttrs.indexOf(5 /* AttributeMarker.ProjectAs */);
- // only check for ngProjectAs in attribute names, don't accidentally match attribute's value
- // (attribute names are stored at even indexes)
- if ((ngProjectAsAttrIdx & 1) === 0) {
- return nodeAttrs[ngProjectAsAttrIdx + 1];
- }
- }
- return null;
- }
- function getNameOnlyMarkerIndex(nodeAttrs) {
- for (let i = 0; i < nodeAttrs.length; i++) {
- const nodeAttr = nodeAttrs[i];
- if (isNameOnlyAttributeMarker(nodeAttr)) {
- return i;
- }
- }
- return nodeAttrs.length;
- }
- function matchTemplateAttribute(attrs, name) {
- let i = attrs.indexOf(4 /* AttributeMarker.Template */);
- if (i > -1) {
- i++;
- while (i < attrs.length) {
- const attr = attrs[i];
- // Return in case we checked all template attrs and are switching to the next section in the
- // attrs array (that starts with a number that represents an attribute marker).
- if (typeof attr === 'number')
- return -1;
- if (attr === name)
- return i;
- i++;
- }
- }
- return -1;
- }
- /**
- * Checks whether a selector is inside a CssSelectorList
- * @param selector Selector to be checked.
- * @param list List in which to look for the selector.
- */
- function isSelectorInSelectorList(selector, list) {
- selectorListLoop: for (let i = 0; i < list.length; i++) {
- const currentSelectorInList = list[i];
- if (selector.length !== currentSelectorInList.length) {
- continue;
- }
- for (let j = 0; j < selector.length; j++) {
- if (selector[j] !== currentSelectorInList[j]) {
- continue selectorListLoop;
- }
- }
- return true;
- }
- return false;
- }
- function maybeWrapInNotSelector(isNegativeMode, chunk) {
- return isNegativeMode ? ':not(' + chunk.trim() + ')' : chunk;
- }
- function stringifyCSSSelector(selector) {
- let result = selector[0];
- let i = 1;
- let mode = 2 /* SelectorFlags.ATTRIBUTE */;
- let currentChunk = '';
- let isNegativeMode = false;
- while (i < selector.length) {
- let valueOrMarker = selector[i];
- if (typeof valueOrMarker === 'string') {
- if (mode & 2 /* SelectorFlags.ATTRIBUTE */) {
- const attrValue = selector[++i];
- currentChunk +=
- '[' + valueOrMarker + (attrValue.length > 0 ? '="' + attrValue + '"' : '') + ']';
- }
- else if (mode & 8 /* SelectorFlags.CLASS */) {
- currentChunk += '.' + valueOrMarker;
- }
- else if (mode & 4 /* SelectorFlags.ELEMENT */) {
- currentChunk += ' ' + valueOrMarker;
- }
- }
- else {
- //
- // Append current chunk to the final result in case we come across SelectorFlag, which
- // indicates that the previous section of a selector is over. We need to accumulate content
- // between flags to make sure we wrap the chunk later in :not() selector if needed, e.g.
- // ```
- // ['', Flags.CLASS, '.classA', Flags.CLASS | Flags.NOT, '.classB', '.classC']
- // ```
- // should be transformed to `.classA :not(.classB .classC)`.
- //
- // Note: for negative selector part, we accumulate content between flags until we find the
- // next negative flag. This is needed to support a case where `:not()` rule contains more than
- // one chunk, e.g. the following selector:
- // ```
- // ['', Flags.ELEMENT | Flags.NOT, 'p', Flags.CLASS, 'foo', Flags.CLASS | Flags.NOT, 'bar']
- // ```
- // should be stringified to `:not(p.foo) :not(.bar)`
- //
- if (currentChunk !== '' && !isPositive(valueOrMarker)) {
- result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
- currentChunk = '';
- }
- mode = valueOrMarker;
- // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
- // mode is maintained for remaining chunks of a selector.
- isNegativeMode = isNegativeMode || !isPositive(mode);
- }
- i++;
- }
- if (currentChunk !== '') {
- result += maybeWrapInNotSelector(isNegativeMode, currentChunk);
- }
- return result;
- }
- /**
- * Generates string representation of CSS selector in parsed form.
- *
- * ComponentDef and DirectiveDef are generated with the selector in parsed form to avoid doing
- * additional parsing at runtime (for example, for directive matching). However in some cases (for
- * example, while bootstrapping a component), a string version of the selector is required to query
- * for the host element on the page. This function takes the parsed form of a selector and returns
- * its string representation.
- *
- * @param selectorList selector in parsed form
- * @returns string representation of a given selector
- */
- function stringifyCSSSelectorList(selectorList) {
- return selectorList.map(stringifyCSSSelector).join(',');
- }
- /**
- * Extracts attributes and classes information from a given CSS selector.
- *
- * This function is used while creating a component dynamically. In this case, the host element
- * (that is created dynamically) should contain attributes and classes specified in component's CSS
- * selector.
- *
- * @param selector CSS selector in parsed form (in a form of array)
- * @returns object with `attrs` and `classes` fields that contain extracted information
- */
- function extractAttrsAndClassesFromSelector(selector) {
- const attrs = [];
- const classes = [];
- let i = 1;
- let mode = 2 /* SelectorFlags.ATTRIBUTE */;
- while (i < selector.length) {
- let valueOrMarker = selector[i];
- if (typeof valueOrMarker === 'string') {
- if (mode === 2 /* SelectorFlags.ATTRIBUTE */) {
- if (valueOrMarker !== '') {
- attrs.push(valueOrMarker, selector[++i]);
- }
- }
- else if (mode === 8 /* SelectorFlags.CLASS */) {
- classes.push(valueOrMarker);
- }
- }
- else {
- // According to CssSelector spec, once we come across `SelectorFlags.NOT` flag, the negative
- // mode is maintained for remaining chunks of a selector. Since attributes and classes are
- // extracted only for "positive" part of the selector, we can stop here.
- if (!isPositive(mode))
- break;
- mode = valueOrMarker;
- }
- i++;
- }
- return { attrs, classes };
- }
- /**
- * Create a component definition object.
- *
- *
- * # Example
- * ```
- * class MyComponent {
- * // Generated by Angular Template Compiler
- * // [Symbol] syntax will not be supported by TypeScript until v2.7
- * static ɵcmp = defineComponent({
- * ...
- * });
- * }
- * ```
- * @codeGenApi
- */
- function ɵɵdefineComponent(componentDefinition) {
- return noSideEffects(() => {
- // Initialize ngDevMode. This must be the first statement in ɵɵdefineComponent.
- // See the `initNgDevMode` docstring for more information.
- (typeof ngDevMode === 'undefined' || ngDevMode) && initNgDevMode();
- const baseDef = getNgDirectiveDef(componentDefinition);
- const def = {
- ...baseDef,
- decls: componentDefinition.decls,
- vars: componentDefinition.vars,
- template: componentDefinition.template,
- consts: componentDefinition.consts || null,
- ngContentSelectors: componentDefinition.ngContentSelectors,
- onPush: componentDefinition.changeDetection === ChangeDetectionStrategy.OnPush,
- directiveDefs: null,
- pipeDefs: null,
- dependencies: baseDef.standalone && componentDefinition.dependencies || null,
- getStandaloneInjector: null,
- signals: componentDefinition.signals ?? false,
- data: componentDefinition.data || {},
- encapsulation: componentDefinition.encapsulation || ViewEncapsulation.Emulated,
- styles: componentDefinition.styles || EMPTY_ARRAY,
- _: null,
- schemas: componentDefinition.schemas || null,
- tView: null,
- id: '',
- };
- initFeatures(def);
- const dependencies = componentDefinition.dependencies;
- def.directiveDefs = extractDefListOrFactory(dependencies, /* pipeDef */ false);
- def.pipeDefs = extractDefListOrFactory(dependencies, /* pipeDef */ true);
- def.id = getComponentId(def);
- return def;
- });
- }
- /**
- * Generated next to NgModules to monkey-patch directive and pipe references onto a component's
- * definition, when generating a direct reference in the component file would otherwise create an
- * import cycle.
- *
- * See [this explanation](https://hackmd.io/Odw80D0pR6yfsOjg_7XCJg?view) for more details.
- *
- * @codeGenApi
- */
- function ɵɵsetComponentScope(type, directives, pipes) {
- const def = type.ɵcmp;
- def.directiveDefs = extractDefListOrFactory(directives, /* pipeDef */ false);
- def.pipeDefs = extractDefListOrFactory(pipes, /* pipeDef */ true);
- }
- function extractDirectiveDef(type) {
- return getComponentDef$1(type) || getDirectiveDef(type);
- }
- function nonNull(value) {
- return value !== null;
- }
- /**
- * @codeGenApi
- */
- function ɵɵdefineNgModule(def) {
- return noSideEffects(() => {
- const res = {
- type: def.type,
- bootstrap: def.bootstrap || EMPTY_ARRAY,
- declarations: def.declarations || EMPTY_ARRAY,
- imports: def.imports || EMPTY_ARRAY,
- exports: def.exports || EMPTY_ARRAY,
- transitiveCompileScopes: null,
- schemas: def.schemas || null,
- id: def.id || null,
- };
- return res;
- });
- }
- /**
- * Adds the module metadata that is necessary to compute the module's transitive scope to an
- * existing module definition.
- *
- * Scope metadata of modules is not used in production builds, so calls to this function can be
- * marked pure to tree-shake it from the bundle, allowing for all referenced declarations
- * to become eligible for tree-shaking as well.
- *
- * @codeGenApi
- */
- function ɵɵsetNgModuleScope(type, scope) {
- return noSideEffects(() => {
- const ngModuleDef = getNgModuleDef(type, true);
- ngModuleDef.declarations = scope.declarations || EMPTY_ARRAY;
- ngModuleDef.imports = scope.imports || EMPTY_ARRAY;
- ngModuleDef.exports = scope.exports || EMPTY_ARRAY;
- });
- }
- /**
- * Inverts an inputs or outputs lookup such that the keys, which were the
- * minified keys, are part of the values, and the values are parsed so that
- * the publicName of the property is the new key
- *
- * e.g. for
- *
- * ```
- * class Comp {
- * @Input()
- * propName1: string;
- *
- * @Input('publicName2')
- * declaredPropName2: number;
- * }
- * ```
- *
- * will be serialized as
- *
- * ```
- * {
- * propName1: 'propName1',
- * declaredPropName2: ['publicName2', 'declaredPropName2'],
- * }
- * ```
- *
- * which is than translated by the minifier as:
- *
- * ```
- * {
- * minifiedPropName1: 'propName1',
- * minifiedPropName2: ['publicName2', 'declaredPropName2'],
- * }
- * ```
- *
- * becomes: (public name => minifiedName)
- *
- * ```
- * {
- * 'propName1': 'minifiedPropName1',
- * 'publicName2': 'minifiedPropName2',
- * }
- * ```
- *
- * Optionally the function can take `secondary` which will result in: (public name => declared name)
- *
- * ```
- * {
- * 'propName1': 'propName1',
- * 'publicName2': 'declaredPropName2',
- * }
- * ```
- *
- */
- function invertObject(obj, secondary) {
- if (obj == null)
- return EMPTY_OBJ;
- const newLookup = {};
- for (const minifiedKey in obj) {
- if (obj.hasOwnProperty(minifiedKey)) {
- let publicName = obj[minifiedKey];
- let declaredName = publicName;
- if (Array.isArray(publicName)) {
- declaredName = publicName[1];
- publicName = publicName[0];
- }
- newLookup[publicName] = minifiedKey;
- if (secondary) {
- (secondary[publicName] = declaredName);
- }
- }
- }
- return newLookup;
- }
- /**
- * Create a directive definition object.
- *
- * # Example
- * ```ts
- * class MyDirective {
- * // Generated by Angular Template Compiler
- * // [Symbol] syntax will not be supported by TypeScript until v2.7
- * static ɵdir = ɵɵdefineDirective({
- * ...
- * });
- * }
- * ```
- *
- * @codeGenApi
- */
- function ɵɵdefineDirective(directiveDefinition) {
- return noSideEffects(() => {
- const def = getNgDirectiveDef(directiveDefinition);
- initFeatures(def);
- return def;
- });
- }
- /**
- * Create a pipe definition object.
- *
- * # Example
- * ```
- * class MyPipe implements PipeTransform {
- * // Generated by Angular Template Compiler
- * static ɵpipe = definePipe({
- * ...
- * });
- * }
- * ```
- * @param pipeDef Pipe definition generated by the compiler
- *
- * @codeGenApi
- */
- function ɵɵdefinePipe(pipeDef) {
- return {
- type: pipeDef.type,
- name: pipeDef.name,
- factory: null,
- pure: pipeDef.pure !== false,
- standalone: pipeDef.standalone === true,
- onDestroy: pipeDef.type.prototype.ngOnDestroy || null
- };
- }
- /**
- * The following getter methods retrieve the definition from the type. Currently the retrieval
- * honors inheritance, but in the future we may change the rule to require that definitions are
- * explicit. This would require some sort of migration strategy.
- */
- function getComponentDef$1(type) {
- return type[NG_COMP_DEF] || null;
- }
- function getDirectiveDef(type) {
- return type[NG_DIR_DEF] || null;
- }
- function getPipeDef$1(type) {
- return type[NG_PIPE_DEF] || null;
- }
- /**
- * Checks whether a given Component, Directive or Pipe is marked as standalone.
- * This will return false if passed anything other than a Component, Directive, or Pipe class
- * See [this guide](/guide/standalone-components) for additional information:
- *
- * @param type A reference to a Component, Directive or Pipe.
- * @publicApi
- */
- function isStandalone(type) {
- const def = getComponentDef$1(type) || getDirectiveDef(type) || getPipeDef$1(type);
- return def !== null ? def.standalone : false;
- }
- function getNgModuleDef(type, throwNotFound) {
- const ngModuleDef = type[NG_MOD_DEF] || null;
- if (!ngModuleDef && throwNotFound === true) {
- throw new Error(`Type ${stringify(type)} does not have 'ɵmod' property.`);
- }
- return ngModuleDef;
- }
- function getNgDirectiveDef(directiveDefinition) {
- const declaredInputs = {};
- return {
- type: directiveDefinition.type,
- providersResolver: null,
- factory: null,
- hostBindings: directiveDefinition.hostBindings || null,
- hostVars: directiveDefinition.hostVars || 0,
- hostAttrs: directiveDefinition.hostAttrs || null,
- contentQueries: directiveDefinition.contentQueries || null,
- declaredInputs,
- inputTransforms: null,
- inputConfig: directiveDefinition.inputs || EMPTY_OBJ,
- exportAs: directiveDefinition.exportAs || null,
- standalone: directiveDefinition.standalone === true,
- signals: directiveDefinition.signals === true,
- selectors: directiveDefinition.selectors || EMPTY_ARRAY,
- viewQuery: directiveDefinition.viewQuery || null,
- features: directiveDefinition.features || null,
- setInput: null,
- findHostDirectiveDefs: null,
- hostDirectives: null,
- inputs: invertObject(directiveDefinition.inputs, declaredInputs),
- outputs: invertObject(directiveDefinition.outputs),
- };
- }
- function initFeatures(definition) {
- definition.features?.forEach((fn) => fn(definition));
- }
- function extractDefListOrFactory(dependencies, pipeDef) {
- if (!dependencies) {
- return null;
- }
- const defExtractor = pipeDef ? getPipeDef$1 : extractDirectiveDef;
- return () => (typeof dependencies === 'function' ? dependencies() : dependencies)
- .map(dep => defExtractor(dep))
- .filter(nonNull);
- }
- /**
- * A map that contains the generated component IDs and type.
- */
- const GENERATED_COMP_IDS = new Map();
- /**
- * A method can returns a component ID from the component definition using a variant of DJB2 hash
- * algorithm.
- */
- function getComponentId(componentDef) {
- let hash = 0;
- // We cannot rely solely on the component selector as the same selector can be used in different
- // modules.
- //
- // `componentDef.style` is not used, due to it causing inconsistencies. Ex: when server
- // component styles has no sourcemaps and browsers do.
- //
- // Example:
- // https://github.com/angular/components/blob/d9f82c8f95309e77a6d82fd574c65871e91354c2/src/material/core/option/option.ts#L248
- // https://github.com/angular/components/blob/285f46dc2b4c5b127d356cb7c4714b221f03ce50/src/material/legacy-core/option/option.ts#L32
- const hashSelectors = [
- componentDef.selectors,
- componentDef.ngContentSelectors,
- componentDef.hostVars,
- componentDef.hostAttrs,
- componentDef.consts,
- componentDef.vars,
- componentDef.decls,
- componentDef.encapsulation,
- componentDef.standalone,
- componentDef.signals,
- componentDef.exportAs,
- JSON.stringify(componentDef.inputs),
- JSON.stringify(componentDef.outputs),
- // We cannot use 'componentDef.type.name' as the name of the symbol will change and will not
- // match in the server and browser bundles.
- Object.getOwnPropertyNames(componentDef.type.prototype),
- !!componentDef.contentQueries,
- !!componentDef.viewQuery,
- ].join('|');
- for (const char of hashSelectors) {
- hash = Math.imul(31, hash) + char.charCodeAt(0) << 0;
- }
- // Force positive number hash.
- // 2147483647 = equivalent of Integer.MAX_VALUE.
- hash += 2147483647 + 1;
- const compId = 'c' + hash;
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- if (GENERATED_COMP_IDS.has(compId)) {
- const previousCompDefType = GENERATED_COMP_IDS.get(compId);
- if (previousCompDefType !== componentDef.type) {
- console.warn(formatRuntimeError(-912 /* RuntimeErrorCode.COMPONENT_ID_COLLISION */, `Component ID generation collision detected. Components '${previousCompDefType.name}' and '${componentDef.type.name}' with selector '${stringifyCSSSelectorList(componentDef
- .selectors)}' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID.`));
- }
- }
- else {
- GENERATED_COMP_IDS.set(compId, componentDef.type);
- }
- }
- return compId;
- }
- // Below are constants for LView indices to help us look up LView members
- // without having to remember the specific indices.
- // Uglify will inline these when minifying so there shouldn't be a cost.
- const HOST = 0;
- const TVIEW = 1;
- const FLAGS = 2;
- const PARENT = 3;
- const NEXT = 4;
- const DESCENDANT_VIEWS_TO_REFRESH = 5;
- const T_HOST = 6;
- const CLEANUP = 7;
- const CONTEXT = 8;
- const INJECTOR$1 = 9;
- const ENVIRONMENT = 10;
- const RENDERER = 11;
- const CHILD_HEAD = 12;
- const CHILD_TAIL = 13;
- // FIXME(misko): Investigate if the three declarations aren't all same thing.
- const DECLARATION_VIEW = 14;
- const DECLARATION_COMPONENT_VIEW = 15;
- const DECLARATION_LCONTAINER = 16;
- const PREORDER_HOOK_FLAGS = 17;
- const QUERIES = 18;
- const ID = 19;
- const EMBEDDED_VIEW_INJECTOR = 20;
- const ON_DESTROY_HOOKS = 21;
- const HYDRATION = 22;
- const REACTIVE_TEMPLATE_CONSUMER = 23;
- const REACTIVE_HOST_BINDING_CONSUMER = 24;
- /**
- * Size of LView's header. Necessary to adjust for it when setting slots.
- *
- * IMPORTANT: `HEADER_OFFSET` should only be referred to the in the `ɵɵ*` instructions to translate
- * instruction index into `LView` index. All other indexes should be in the `LView` index space and
- * there should be no need to refer to `HEADER_OFFSET` anywhere else.
- */
- const HEADER_OFFSET = 25;
- // Note: This hack is necessary so we don't erroneously get a circular dependency
- // failure based on types.
- const unusedValueExportToPlacateAjd$4 = 1;
- /**
- * Special location which allows easy identification of type. If we have an array which was
- * retrieved from the `LView` and that array has `true` at `TYPE` location, we know it is
- * `LContainer`.
- */
- const TYPE = 1;
- /**
- * Below are constants for LContainer indices to help us look up LContainer members
- * without having to remember the specific indices.
- * Uglify will inline these when minifying so there shouldn't be a cost.
- */
- /**
- * Flag to signify that this `LContainer` may have transplanted views which need to be change
- * detected. (see: `LView[DECLARATION_COMPONENT_VIEW])`.
- *
- * This flag, once set, is never unset for the `LContainer`. This means that when unset we can skip
- * a lot of work in `refreshEmbeddedViews`. But when set we still need to verify
- * that the `MOVED_VIEWS` are transplanted and on-push.
- */
- const HAS_TRANSPLANTED_VIEWS = 2;
- // PARENT, NEXT, DESCENDANT_VIEWS_TO_REFRESH are indices 3, 4, and 5
- // As we already have these constants in LView, we don't need to re-create them.
- // T_HOST is index 6
- // We already have this constants in LView, we don't need to re-create it.
- const NATIVE = 7;
- const VIEW_REFS = 8;
- const MOVED_VIEWS = 9;
- const DEHYDRATED_VIEWS = 10;
- /**
- * Size of LContainer's header. Represents the index after which all views in the
- * container will be inserted. We need to keep a record of current views so we know
- * which views are already in the DOM (and don't need to be re-added) and so we can
- * remove views from the DOM when they are no longer required.
- */
- const CONTAINER_HEADER_OFFSET = 11;
- // Note: This hack is necessary so we don't erroneously get a circular dependency
- // failure based on types.
- const unusedValueExportToPlacateAjd$3 = 1;
- /**
- * True if `value` is `LView`.
- * @param value wrapped value of `RNode`, `LView`, `LContainer`
- */
- function isLView(value) {
- return Array.isArray(value) && typeof value[TYPE] === 'object';
- }
- /**
- * True if `value` is `LContainer`.
- * @param value wrapped value of `RNode`, `LView`, `LContainer`
- */
- function isLContainer(value) {
- return Array.isArray(value) && value[TYPE] === true;
- }
- function isContentQueryHost(tNode) {
- return (tNode.flags & 4 /* TNodeFlags.hasContentQuery */) !== 0;
- }
- function isComponentHost(tNode) {
- return tNode.componentOffset > -1;
- }
- function isDirectiveHost(tNode) {
- return (tNode.flags & 1 /* TNodeFlags.isDirectiveHost */) === 1 /* TNodeFlags.isDirectiveHost */;
- }
- function isComponentDef(def) {
- return !!def.template;
- }
- function isRootView(target) {
- return (target[FLAGS] & 512 /* LViewFlags.IsRoot */) !== 0;
- }
- function isProjectionTNode(tNode) {
- return (tNode.type & 16 /* TNodeType.Projection */) === 16 /* TNodeType.Projection */;
- }
- function hasI18n(lView) {
- return (lView[FLAGS] & 32 /* LViewFlags.HasI18n */) === 32 /* LViewFlags.HasI18n */;
- }
- // [Assert functions do not constraint type when they are guarded by a truthy
- // expression.](https://github.com/microsoft/TypeScript/issues/37295)
- function assertTNodeForLView(tNode, lView) {
- assertTNodeForTView(tNode, lView[TVIEW]);
- }
- function assertTNodeForTView(tNode, tView) {
- assertTNode(tNode);
- const tData = tView.data;
- for (let i = HEADER_OFFSET; i < tData.length; i++) {
- if (tData[i] === tNode) {
- return;
- }
- }
- throwError('This TNode does not belong to this TView.');
- }
- function assertTNode(tNode) {
- assertDefined(tNode, 'TNode must be defined');
- if (!(tNode && typeof tNode === 'object' && tNode.hasOwnProperty('directiveStylingLast'))) {
- throwError('Not of type TNode, got: ' + tNode);
- }
- }
- function assertTIcu(tIcu) {
- assertDefined(tIcu, 'Expected TIcu to be defined');
- if (!(typeof tIcu.currentCaseLViewIndex === 'number')) {
- throwError('Object is not of TIcu type.');
- }
- }
- function assertComponentType(actual, msg = 'Type passed in is not ComponentType, it does not have \'ɵcmp\' property.') {
- if (!getComponentDef$1(actual)) {
- throwError(msg);
- }
- }
- function assertNgModuleType(actual, msg = 'Type passed in is not NgModuleType, it does not have \'ɵmod\' property.') {
- if (!getNgModuleDef(actual)) {
- throwError(msg);
- }
- }
- function assertCurrentTNodeIsParent(isParent) {
- assertEqual(isParent, true, 'currentTNode should be a parent');
- }
- function assertHasParent(tNode) {
- assertDefined(tNode, 'currentTNode should exist!');
- assertDefined(tNode.parent, 'currentTNode should have a parent');
- }
- function assertLContainer(value) {
- assertDefined(value, 'LContainer must be defined');
- assertEqual(isLContainer(value), true, 'Expecting LContainer');
- }
- function assertLViewOrUndefined(value) {
- value && assertEqual(isLView(value), true, 'Expecting LView or undefined or null');
- }
- function assertLView(value) {
- assertDefined(value, 'LView must be defined');
- assertEqual(isLView(value), true, 'Expecting LView');
- }
- function assertFirstCreatePass(tView, errMessage) {
- assertEqual(tView.firstCreatePass, true, errMessage || 'Should only be called in first create pass.');
- }
- function assertFirstUpdatePass(tView, errMessage) {
- assertEqual(tView.firstUpdatePass, true, errMessage || 'Should only be called in first update pass.');
- }
- /**
- * This is a basic sanity check that an object is probably a directive def. DirectiveDef is
- * an interface, so we can't do a direct instanceof check.
- */
- function assertDirectiveDef(obj) {
- if (obj.type === undefined || obj.selectors == undefined || obj.inputs === undefined) {
- throwError(`Expected a DirectiveDef/ComponentDef and this object does not seem to have the expected shape.`);
- }
- }
- function assertIndexInDeclRange(lView, index) {
- const tView = lView[1];
- assertBetween(HEADER_OFFSET, tView.bindingStartIndex, index);
- }
- function assertIndexInExpandoRange(lView, index) {
- const tView = lView[1];
- assertBetween(tView.expandoStartIndex, lView.length, index);
- }
- function assertBetween(lower, upper, index) {
- if (!(lower <= index && index < upper)) {
- throwError(`Index out of range (expecting ${lower} <= ${index} < ${upper})`);
- }
- }
- function assertProjectionSlots(lView, errMessage) {
- assertDefined(lView[DECLARATION_COMPONENT_VIEW], 'Component views should exist.');
- assertDefined(lView[DECLARATION_COMPONENT_VIEW][T_HOST].projection, errMessage ||
- 'Components with projection nodes (<ng-content>) must have projection slots defined.');
- }
- function assertParentView(lView, errMessage) {
- assertDefined(lView, errMessage || 'Component views should always have a parent view (component\'s host view)');
- }
- /**
- * This is a basic sanity check that the `injectorIndex` seems to point to what looks like a
- * NodeInjector data structure.
- *
- * @param lView `LView` which should be checked.
- * @param injectorIndex index into the `LView` where the `NodeInjector` is expected.
- */
- function assertNodeInjector(lView, injectorIndex) {
- assertIndexInExpandoRange(lView, injectorIndex);
- assertIndexInExpandoRange(lView, injectorIndex + 8 /* NodeInjectorOffset.PARENT */);
- assertNumber(lView[injectorIndex + 0], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 1], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 2], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 3], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 4], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 5], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 6], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 7], 'injectorIndex should point to a bloom filter');
- assertNumber(lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */], 'injectorIndex should point to parent injector');
- }
- function getFactoryDef(type, throwNotFound) {
- const hasFactoryDef = type.hasOwnProperty(NG_FACTORY_DEF);
- if (!hasFactoryDef && throwNotFound === true && ngDevMode) {
- throw new Error(`Type ${stringify(type)} does not have 'ɵfac' property.`);
- }
- return hasFactoryDef ? type[NG_FACTORY_DEF] : null;
- }
- /**
- * Symbol used to tell `Signal`s apart from other functions.
- *
- * This can be used to auto-unwrap signals in various cases, or to auto-wrap non-signal values.
- */
- const SIGNAL = /* @__PURE__ */ Symbol('SIGNAL');
- /**
- * Checks if the given `value` is a reactive `Signal`.
- *
- * @developerPreview
- */
- function isSignal(value) {
- return typeof value === 'function' && value[SIGNAL] !== undefined;
- }
- /**
- * The default equality function used for `signal` and `computed`, which treats objects and arrays
- * as never equal, and all other primitive values using identity semantics.
- *
- * This allows signals to hold non-primitive values (arrays, objects, other collections) and still
- * propagate change notification upon explicit mutation without identity change.
- *
- * @developerPreview
- */
- function defaultEquals(a, b) {
- // `Object.is` compares two values using identity semantics which is desired behavior for
- // primitive values. If `Object.is` determines two values to be equal we need to make sure that
- // those don't represent objects (we want to make sure that 2 objects are always considered
- // "unequal"). The null check is needed for the special case of JavaScript reporting null values
- // as objects (`typeof null === 'object'`).
- return (a === null || typeof a !== 'object') && Object.is(a, b);
- }
- // Required as the signals library is in a separate package, so we need to explicitly ensure the
- /**
- * The currently active consumer `ReactiveNode`, if running code in a reactive context.
- *
- * Change this via `setActiveConsumer`.
- */
- let activeConsumer = null;
- let inNotificationPhase = false;
- function setActiveConsumer(consumer) {
- const prev = activeConsumer;
- activeConsumer = consumer;
- return prev;
- }
- const REACTIVE_NODE = {
- version: 0,
- dirty: false,
- producerNode: undefined,
- producerLastReadVersion: undefined,
- producerIndexOfThis: undefined,
- nextProducerIndex: 0,
- liveConsumerNode: undefined,
- liveConsumerIndexOfThis: undefined,
- consumerAllowSignalWrites: false,
- consumerIsAlwaysLive: false,
- producerMustRecompute: () => false,
- producerRecomputeValue: () => { },
- consumerMarkedDirty: () => { },
- };
- /**
- * Called by implementations when a producer's signal is read.
- */
- function producerAccessed(node) {
- if (inNotificationPhase) {
- throw new Error(typeof ngDevMode !== 'undefined' && ngDevMode ?
- `Assertion error: signal read during notification phase` :
- '');
- }
- if (activeConsumer === null) {
- // Accessed outside of a reactive context, so nothing to record.
- return;
- }
- // This producer is the `idx`th dependency of `activeConsumer`.
- const idx = activeConsumer.nextProducerIndex++;
- assertConsumerNode(activeConsumer);
- if (idx < activeConsumer.producerNode.length && activeConsumer.producerNode[idx] !== node) {
- // There's been a change in producers since the last execution of `activeConsumer`.
- // `activeConsumer.producerNode[idx]` holds a stale dependency which will be be removed and
- // replaced with `this`.
- //
- // If `activeConsumer` isn't live, then this is a no-op, since we can replace the producer in
- // `activeConsumer.producerNode` directly. However, if `activeConsumer` is live, then we need
- // to remove it from the stale producer's `liveConsumer`s.
- if (consumerIsLive(activeConsumer)) {
- const staleProducer = activeConsumer.producerNode[idx];
- producerRemoveLiveConsumerAtIndex(staleProducer, activeConsumer.producerIndexOfThis[idx]);
- // At this point, the only record of `staleProducer` is the reference at
- // `activeConsumer.producerNode[idx]` which will be overwritten below.
- }
- }
- if (activeConsumer.producerNode[idx] !== node) {
- // We're a new dependency of the consumer (at `idx`).
- activeConsumer.producerNode[idx] = node;
- // If the active consumer is live, then add it as a live consumer. If not, then use 0 as a
- // placeholder value.
- activeConsumer.producerIndexOfThis[idx] =
- consumerIsLive(activeConsumer) ? producerAddLiveConsumer(node, activeConsumer, idx) : 0;
- }
- activeConsumer.producerLastReadVersion[idx] = node.version;
- }
- /**
- * Ensure this producer's `version` is up-to-date.
- */
- function producerUpdateValueVersion(node) {
- if (consumerIsLive(node) && !node.dirty) {
- // A live consumer will be marked dirty by producers, so a clean state means that its version
- // is guaranteed to be up-to-date.
- return;
- }
- if (!node.producerMustRecompute(node) && !consumerPollProducersForChange(node)) {
- // None of our producers report a change since the last time they were read, so no
- // recomputation of our value is necessary, and we can consider ourselves clean.
- node.dirty = false;
- return;
- }
- node.producerRecomputeValue(node);
- // After recomputing the value, we're no longer dirty.
- node.dirty = false;
- }
- /**
- * Propagate a dirty notification to live consumers of this producer.
- */
- function producerNotifyConsumers(node) {
- if (node.liveConsumerNode === undefined) {
- return;
- }
- // Prevent signal reads when we're updating the graph
- const prev = inNotificationPhase;
- inNotificationPhase = true;
- try {
- for (const consumer of node.liveConsumerNode) {
- if (!consumer.dirty) {
- consumerMarkDirty(consumer);
- }
- }
- }
- finally {
- inNotificationPhase = prev;
- }
- }
- /**
- * Whether this `ReactiveNode` in its producer capacity is currently allowed to initiate updates,
- * based on the current consumer context.
- */
- function producerUpdatesAllowed() {
- return activeConsumer?.consumerAllowSignalWrites !== false;
- }
- function consumerMarkDirty(node) {
- node.dirty = true;
- producerNotifyConsumers(node);
- node.consumerMarkedDirty?.(node);
- }
- /**
- * Prepare this consumer to run a computation in its reactive context.
- *
- * Must be called by subclasses which represent reactive computations, before those computations
- * begin.
- */
- function consumerBeforeComputation(node) {
- node && (node.nextProducerIndex = 0);
- return setActiveConsumer(node);
- }
- /**
- * Finalize this consumer's state after a reactive computation has run.
- *
- * Must be called by subclasses which represent reactive computations, after those computations
- * have finished.
- */
- function consumerAfterComputation(node, prevConsumer) {
- setActiveConsumer(prevConsumer);
- if (!node || node.producerNode === undefined || node.producerIndexOfThis === undefined ||
- node.producerLastReadVersion === undefined) {
- return;
- }
- if (consumerIsLive(node)) {
- // For live consumers, we need to remove the producer -> consumer edge for any stale producers
- // which weren't dependencies after the recomputation.
- for (let i = node.nextProducerIndex; i < node.producerNode.length; i++) {
- producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
- }
- }
- // Truncate the producer tracking arrays.
- // Perf note: this is essentially truncating the length to `node.nextProducerIndex`, but
- // benchmarking has shown that individual pop operations are faster.
- while (node.producerNode.length > node.nextProducerIndex) {
- node.producerNode.pop();
- node.producerLastReadVersion.pop();
- node.producerIndexOfThis.pop();
- }
- }
- /**
- * Determine whether this consumer has any dependencies which have changed since the last time
- * they were read.
- */
- function consumerPollProducersForChange(node) {
- assertConsumerNode(node);
- // Poll producers for change.
- for (let i = 0; i < node.producerNode.length; i++) {
- const producer = node.producerNode[i];
- const seenVersion = node.producerLastReadVersion[i];
- // First check the versions. A mismatch means that the producer's value is known to have
- // changed since the last time we read it.
- if (seenVersion !== producer.version) {
- return true;
- }
- // The producer's version is the same as the last time we read it, but it might itself be
- // stale. Force the producer to recompute its version (calculating a new value if necessary).
- producerUpdateValueVersion(producer);
- // Now when we do this check, `producer.version` is guaranteed to be up to date, so if the
- // versions still match then it has not changed since the last time we read it.
- if (seenVersion !== producer.version) {
- return true;
- }
- }
- return false;
- }
- /**
- * Disconnect this consumer from the graph.
- */
- function consumerDestroy(node) {
- assertConsumerNode(node);
- if (consumerIsLive(node)) {
- // Drop all connections from the graph to this node.
- for (let i = 0; i < node.producerNode.length; i++) {
- producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
- }
- }
- // Truncate all the arrays to drop all connection from this node to the graph.
- node.producerNode.length = node.producerLastReadVersion.length = node.producerIndexOfThis.length =
- 0;
- if (node.liveConsumerNode) {
- node.liveConsumerNode.length = node.liveConsumerIndexOfThis.length = 0;
- }
- }
- /**
- * Add `consumer` as a live consumer of this node.
- *
- * Note that this operation is potentially transitive. If this node becomes live, then it becomes
- * a live consumer of all of its current producers.
- */
- function producerAddLiveConsumer(node, consumer, indexOfThis) {
- assertProducerNode(node);
- assertConsumerNode(node);
- if (node.liveConsumerNode.length === 0) {
- // When going from 0 to 1 live consumers, we become a live consumer to our producers.
- for (let i = 0; i < node.producerNode.length; i++) {
- node.producerIndexOfThis[i] = producerAddLiveConsumer(node.producerNode[i], node, i);
- }
- }
- node.liveConsumerIndexOfThis.push(indexOfThis);
- return node.liveConsumerNode.push(consumer) - 1;
- }
- /**
- * Remove the live consumer at `idx`.
- */
- function producerRemoveLiveConsumerAtIndex(node, idx) {
- assertProducerNode(node);
- assertConsumerNode(node);
- if (typeof ngDevMode !== 'undefined' && ngDevMode && idx >= node.liveConsumerNode.length) {
- throw new Error(`Assertion error: active consumer index ${idx} is out of bounds of ${node.liveConsumerNode.length} consumers)`);
- }
- if (node.liveConsumerNode.length === 1) {
- // When removing the last live consumer, we will no longer be live. We need to remove
- // ourselves from our producers' tracking (which may cause consumer-producers to lose
- // liveness as well).
- for (let i = 0; i < node.producerNode.length; i++) {
- producerRemoveLiveConsumerAtIndex(node.producerNode[i], node.producerIndexOfThis[i]);
- }
- }
- // Move the last value of `liveConsumers` into `idx`. Note that if there's only a single
- // live consumer, this is a no-op.
- const lastIdx = node.liveConsumerNode.length - 1;
- node.liveConsumerNode[idx] = node.liveConsumerNode[lastIdx];
- node.liveConsumerIndexOfThis[idx] = node.liveConsumerIndexOfThis[lastIdx];
- // Truncate the array.
- node.liveConsumerNode.length--;
- node.liveConsumerIndexOfThis.length--;
- // If the index is still valid, then we need to fix the index pointer from the producer to this
- // consumer, and update it from `lastIdx` to `idx` (accounting for the move above).
- if (idx < node.liveConsumerNode.length) {
- const idxProducer = node.liveConsumerIndexOfThis[idx];
- const consumer = node.liveConsumerNode[idx];
- assertConsumerNode(consumer);
- consumer.producerIndexOfThis[idxProducer] = idx;
- }
- }
- function consumerIsLive(node) {
- return node.consumerIsAlwaysLive || (node?.liveConsumerNode?.length ?? 0) > 0;
- }
- function assertConsumerNode(node) {
- node.producerNode ??= [];
- node.producerIndexOfThis ??= [];
- node.producerLastReadVersion ??= [];
- }
- function assertProducerNode(node) {
- node.liveConsumerNode ??= [];
- node.liveConsumerIndexOfThis ??= [];
- }
- /**
- * Create a computed `Signal` which derives a reactive value from an expression.
- *
- * @developerPreview
- */
- function computed(computation, options) {
- const node = Object.create(COMPUTED_NODE);
- node.computation = computation;
- options?.equal && (node.equal = options.equal);
- const computed = () => {
- // Check if the value needs updating before returning it.
- producerUpdateValueVersion(node);
- // Record that someone looked at this signal.
- producerAccessed(node);
- if (node.value === ERRORED) {
- throw node.error;
- }
- return node.value;
- };
- computed[SIGNAL] = node;
- return computed;
- }
- /**
- * A dedicated symbol used before a computed value has been calculated for the first time.
- * Explicitly typed as `any` so we can use it as signal's value.
- */
- const UNSET = /* @__PURE__ */ Symbol('UNSET');
- /**
- * A dedicated symbol used in place of a computed signal value to indicate that a given computation
- * is in progress. Used to detect cycles in computation chains.
- * Explicitly typed as `any` so we can use it as signal's value.
- */
- const COMPUTING = /* @__PURE__ */ Symbol('COMPUTING');
- /**
- * A dedicated symbol used in place of a computed signal value to indicate that a given computation
- * failed. The thrown error is cached until the computation gets dirty again.
- * Explicitly typed as `any` so we can use it as signal's value.
- */
- const ERRORED = /* @__PURE__ */ Symbol('ERRORED');
- // Note: Using an IIFE here to ensure that the spread assignment is not considered
- // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
- // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
- const COMPUTED_NODE = /* @__PURE__ */ (() => {
- return {
- ...REACTIVE_NODE,
- value: UNSET,
- dirty: true,
- error: null,
- equal: defaultEquals,
- producerMustRecompute(node) {
- // Force a recomputation if there's no current value, or if the current value is in the
- // process of being calculated (which should throw an error).
- return node.value === UNSET || node.value === COMPUTING;
- },
- producerRecomputeValue(node) {
- if (node.value === COMPUTING) {
- // Our computation somehow led to a cyclic read of itself.
- throw new Error('Detected cycle in computations.');
- }
- const oldValue = node.value;
- node.value = COMPUTING;
- const prevConsumer = consumerBeforeComputation(node);
- let newValue;
- try {
- newValue = node.computation();
- }
- catch (err) {
- newValue = ERRORED;
- node.error = err;
- }
- finally {
- consumerAfterComputation(node, prevConsumer);
- }
- if (oldValue !== UNSET && oldValue !== ERRORED && newValue !== ERRORED &&
- node.equal(oldValue, newValue)) {
- // No change to `valueVersion` - old and new values are
- // semantically equivalent.
- node.value = oldValue;
- return;
- }
- node.value = newValue;
- node.version++;
- },
- };
- })();
- function defaultThrowError() {
- throw new Error();
- }
- let throwInvalidWriteToSignalErrorFn = defaultThrowError;
- function throwInvalidWriteToSignalError() {
- throwInvalidWriteToSignalErrorFn();
- }
- function setThrowInvalidWriteToSignalError(fn) {
- throwInvalidWriteToSignalErrorFn = fn;
- }
- /**
- * If set, called after `WritableSignal`s are updated.
- *
- * This hook can be used to achieve various effects, such as running effects synchronously as part
- * of setting a signal.
- */
- let postSignalSetFn = null;
- /**
- * Create a `Signal` that can be set or updated directly.
- *
- * @developerPreview
- */
- function signal(initialValue, options) {
- const node = Object.create(SIGNAL_NODE);
- node.value = initialValue;
- options?.equal && (node.equal = options.equal);
- function signalFn() {
- producerAccessed(node);
- return node.value;
- }
- signalFn.set = signalSetFn;
- signalFn.update = signalUpdateFn;
- signalFn.mutate = signalMutateFn;
- signalFn.asReadonly = signalAsReadonlyFn;
- signalFn[SIGNAL] = node;
- return signalFn;
- }
- function setPostSignalSetFn(fn) {
- const prev = postSignalSetFn;
- postSignalSetFn = fn;
- return prev;
- }
- // Note: Using an IIFE here to ensure that the spread assignment is not considered
- // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
- // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
- const SIGNAL_NODE = /* @__PURE__ */ (() => {
- return {
- ...REACTIVE_NODE,
- equal: defaultEquals,
- readonlyFn: undefined,
- };
- })();
- function signalValueChanged(node) {
- node.version++;
- producerNotifyConsumers(node);
- postSignalSetFn?.();
- }
- function signalSetFn(newValue) {
- const node = this[SIGNAL];
- if (!producerUpdatesAllowed()) {
- throwInvalidWriteToSignalError();
- }
- if (!node.equal(node.value, newValue)) {
- node.value = newValue;
- signalValueChanged(node);
- }
- }
- function signalUpdateFn(updater) {
- if (!producerUpdatesAllowed()) {
- throwInvalidWriteToSignalError();
- }
- signalSetFn.call(this, updater(this[SIGNAL].value));
- }
- function signalMutateFn(mutator) {
- const node = this[SIGNAL];
- if (!producerUpdatesAllowed()) {
- throwInvalidWriteToSignalError();
- }
- // Mutate bypasses equality checks as it's by definition changing the value.
- mutator(node.value);
- signalValueChanged(node);
- }
- function signalAsReadonlyFn() {
- const node = this[SIGNAL];
- if (node.readonlyFn === undefined) {
- const readonlyFn = () => this();
- readonlyFn[SIGNAL] = node;
- node.readonlyFn = readonlyFn;
- }
- return node.readonlyFn;
- }
- /**
- * Execute an arbitrary function in a non-reactive (non-tracking) context. The executed function
- * can, optionally, return a value.
- *
- * @developerPreview
- */
- function untracked(nonReactiveReadsFn) {
- const prevConsumer = setActiveConsumer(null);
- // We are not trying to catch any particular errors here, just making sure that the consumers
- // stack is restored in case of errors.
- try {
- return nonReactiveReadsFn();
- }
- finally {
- setActiveConsumer(prevConsumer);
- }
- }
- function watch(fn, schedule, allowSignalWrites) {
- const node = Object.create(WATCH_NODE);
- if (allowSignalWrites) {
- node.consumerAllowSignalWrites = true;
- }
- node.fn = fn;
- node.schedule = schedule;
- const registerOnCleanup = (cleanupFn) => {
- node.cleanupFn = cleanupFn;
- };
- const run = () => {
- node.dirty = false;
- if (node.hasRun && !consumerPollProducersForChange(node)) {
- return;
- }
- node.hasRun = true;
- const prevConsumer = consumerBeforeComputation(node);
- try {
- node.cleanupFn();
- node.cleanupFn = NOOP_CLEANUP_FN;
- node.fn(registerOnCleanup);
- }
- finally {
- consumerAfterComputation(node, prevConsumer);
- }
- };
- node.ref = {
- notify: () => consumerMarkDirty(node),
- run,
- cleanup: () => node.cleanupFn(),
- };
- return node.ref;
- }
- const NOOP_CLEANUP_FN = () => { };
- // Note: Using an IIFE here to ensure that the spread assignment is not considered
- // a side-effect, ending up preserving `COMPUTED_NODE` and `REACTIVE_NODE`.
- // TODO: remove when https://github.com/evanw/esbuild/issues/3392 is resolved.
- const WATCH_NODE = /* @__PURE__ */ (() => {
- return {
- ...REACTIVE_NODE,
- consumerIsAlwaysLive: true,
- consumerAllowSignalWrites: false,
- consumerMarkedDirty: (node) => {
- node.schedule(node.ref);
- },
- hasRun: false,
- cleanupFn: NOOP_CLEANUP_FN,
- };
- })();
- function setAlternateWeakRefImpl(impl) {
- // TODO: remove this function
- }
- /**
- * Represents a basic change from a previous to a new value for a single
- * property on a directive instance. Passed as a value in a
- * {@link SimpleChanges} object to the `ngOnChanges` hook.
- *
- * @see {@link OnChanges}
- *
- * @publicApi
- */
- class SimpleChange {
- constructor(previousValue, currentValue, firstChange) {
- this.previousValue = previousValue;
- this.currentValue = currentValue;
- this.firstChange = firstChange;
- }
- /**
- * Check whether the new value is the first value assigned.
- */
- isFirstChange() {
- return this.firstChange;
- }
- }
- /**
- * The NgOnChangesFeature decorates a component with support for the ngOnChanges
- * lifecycle hook, so it should be included in any component that implements
- * that hook.
- *
- * If the component or directive uses inheritance, the NgOnChangesFeature MUST
- * be included as a feature AFTER {@link InheritDefinitionFeature}, otherwise
- * inherited properties will not be propagated to the ngOnChanges lifecycle
- * hook.
- *
- * Example usage:
- *
- * ```
- * static ɵcmp = defineComponent({
- * ...
- * inputs: {name: 'publicName'},
- * features: [NgOnChangesFeature]
- * });
- * ```
- *
- * @codeGenApi
- */
- function ɵɵNgOnChangesFeature() {
- return NgOnChangesFeatureImpl;
- }
- function NgOnChangesFeatureImpl(definition) {
- if (definition.type.prototype.ngOnChanges) {
- definition.setInput = ngOnChangesSetInput;
- }
- return rememberChangeHistoryAndInvokeOnChangesHook;
- }
- // This option ensures that the ngOnChanges lifecycle hook will be inherited
- // from superclasses (in InheritDefinitionFeature).
- /** @nocollapse */
- // tslint:disable-next-line:no-toplevel-property-access
- ɵɵNgOnChangesFeature.ngInherit = true;
- /**
- * This is a synthetic lifecycle hook which gets inserted into `TView.preOrderHooks` to simulate
- * `ngOnChanges`.
- *
- * The hook reads the `NgSimpleChangesStore` data from the component instance and if changes are
- * found it invokes `ngOnChanges` on the component instance.
- *
- * @param this Component instance. Because this function gets inserted into `TView.preOrderHooks`,
- * it is guaranteed to be called with component instance.
- */
- function rememberChangeHistoryAndInvokeOnChangesHook() {
- const simpleChangesStore = getSimpleChangesStore(this);
- const current = simpleChangesStore?.current;
- if (current) {
- const previous = simpleChangesStore.previous;
- if (previous === EMPTY_OBJ) {
- simpleChangesStore.previous = current;
- }
- else {
- // New changes are copied to the previous store, so that we don't lose history for inputs
- // which were not changed this time
- for (let key in current) {
- previous[key] = current[key];
- }
- }
- simpleChangesStore.current = null;
- this.ngOnChanges(current);
- }
- }
- function ngOnChangesSetInput(instance, value, publicName, privateName) {
- const declaredName = this.declaredInputs[publicName];
- ngDevMode && assertString(declaredName, 'Name of input in ngOnChanges has to be a string');
- const simpleChangesStore = getSimpleChangesStore(instance) ||
- setSimpleChangesStore(instance, { previous: EMPTY_OBJ, current: null });
- const current = simpleChangesStore.current || (simpleChangesStore.current = {});
- const previous = simpleChangesStore.previous;
- const previousChange = previous[declaredName];
- current[declaredName] = new SimpleChange(previousChange && previousChange.currentValue, value, previous === EMPTY_OBJ);
- instance[privateName] = value;
- }
- const SIMPLE_CHANGES_STORE = '__ngSimpleChanges__';
- function getSimpleChangesStore(instance) {
- return instance[SIMPLE_CHANGES_STORE] || null;
- }
- function setSimpleChangesStore(instance, store) {
- return instance[SIMPLE_CHANGES_STORE] = store;
- }
- let profilerCallback = null;
- /**
- * Sets the callback function which will be invoked before and after performing certain actions at
- * runtime (for example, before and after running change detection).
- *
- * Warning: this function is *INTERNAL* and should not be relied upon in application's code.
- * The contract of the function might be changed in any release and/or the function can be removed
- * completely.
- *
- * @param profiler function provided by the caller or null value to disable profiling.
- */
- const setProfiler = (profiler) => {
- profilerCallback = profiler;
- };
- /**
- * Profiler function which wraps user code executed by the runtime.
- *
- * @param event ProfilerEvent corresponding to the execution context
- * @param instance component instance
- * @param hookOrListener lifecycle hook function or output listener. The value depends on the
- * execution context
- * @returns
- */
- const profiler = function (event, instance, hookOrListener) {
- if (profilerCallback != null /* both `null` and `undefined` */) {
- profilerCallback(event, instance, hookOrListener);
- }
- };
- const SVG_NAMESPACE = 'svg';
- const MATH_ML_NAMESPACE = 'math';
- /**
- * For efficiency reasons we often put several different data types (`RNode`, `LView`, `LContainer`)
- * in same location in `LView`. This is because we don't want to pre-allocate space for it
- * because the storage is sparse. This file contains utilities for dealing with such data types.
- *
- * How do we know what is stored at a given location in `LView`.
- * - `Array.isArray(value) === false` => `RNode` (The normal storage value)
- * - `Array.isArray(value) === true` => then the `value[0]` represents the wrapped value.
- * - `typeof value[TYPE] === 'object'` => `LView`
- * - This happens when we have a component at a given location
- * - `typeof value[TYPE] === true` => `LContainer`
- * - This happens when we have `LContainer` binding at a given location.
- *
- *
- * NOTE: it is assumed that `Array.isArray` and `typeof` operations are very efficient.
- */
- /**
- * Returns `RNode`.
- * @param value wrapped value of `RNode`, `LView`, `LContainer`
- */
- function unwrapRNode(value) {
- while (Array.isArray(value)) {
- value = value[HOST];
- }
- return value;
- }
- /**
- * Returns `LView` or `null` if not found.
- * @param value wrapped value of `RNode`, `LView`, `LContainer`
- */
- function unwrapLView(value) {
- while (Array.isArray(value)) {
- // This check is same as `isLView()` but we don't call at as we don't want to call
- // `Array.isArray()` twice and give JITer more work for inlining.
- if (typeof value[TYPE] === 'object')
- return value;
- value = value[HOST];
- }
- return null;
- }
- /**
- * Retrieves an element value from the provided `viewData`, by unwrapping
- * from any containers, component views, or style contexts.
- */
- function getNativeByIndex(index, lView) {
- ngDevMode && assertIndexInRange(lView, index);
- ngDevMode && assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Expected to be past HEADER_OFFSET');
- return unwrapRNode(lView[index]);
- }
- /**
- * Retrieve an `RNode` for a given `TNode` and `LView`.
- *
- * This function guarantees in dev mode to retrieve a non-null `RNode`.
- *
- * @param tNode
- * @param lView
- */
- function getNativeByTNode(tNode, lView) {
- ngDevMode && assertTNodeForLView(tNode, lView);
- ngDevMode && assertIndexInRange(lView, tNode.index);
- const node = unwrapRNode(lView[tNode.index]);
- return node;
- }
- /**
- * Retrieve an `RNode` or `null` for a given `TNode` and `LView`.
- *
- * Some `TNode`s don't have associated `RNode`s. For example `Projection`
- *
- * @param tNode
- * @param lView
- */
- function getNativeByTNodeOrNull(tNode, lView) {
- const index = tNode === null ? -1 : tNode.index;
- if (index !== -1) {
- ngDevMode && assertTNodeForLView(tNode, lView);
- const node = unwrapRNode(lView[index]);
- return node;
- }
- return null;
- }
- // fixme(misko): The return Type should be `TNode|null`
- function getTNode(tView, index) {
- ngDevMode && assertGreaterThan(index, -1, 'wrong index for TNode');
- ngDevMode && assertLessThan(index, tView.data.length, 'wrong index for TNode');
- const tNode = tView.data[index];
- ngDevMode && tNode !== null && assertTNode(tNode);
- return tNode;
- }
- /** Retrieves a value from any `LView` or `TData`. */
- function load(view, index) {
- ngDevMode && assertIndexInRange(view, index);
- return view[index];
- }
- function getComponentLViewByIndex(nodeIndex, hostView) {
- // Could be an LView or an LContainer. If LContainer, unwrap to find LView.
- ngDevMode && assertIndexInRange(hostView, nodeIndex);
- const slotValue = hostView[nodeIndex];
- const lView = isLView(slotValue) ? slotValue : slotValue[HOST];
- return lView;
- }
- /** Checks whether a given view is in creation mode */
- function isCreationMode(view) {
- return (view[FLAGS] & 4 /* LViewFlags.CreationMode */) === 4 /* LViewFlags.CreationMode */;
- }
- /**
- * Returns a boolean for whether the view is attached to the change detection tree.
- *
- * Note: This determines whether a view should be checked, not whether it's inserted
- * into a container. For that, you'll want `viewAttachedToContainer` below.
- */
- function viewAttachedToChangeDetector(view) {
- return (view[FLAGS] & 128 /* LViewFlags.Attached */) === 128 /* LViewFlags.Attached */;
- }
- /** Returns a boolean for whether the view is attached to a container. */
- function viewAttachedToContainer(view) {
- return isLContainer(view[PARENT]);
- }
- function getConstant(consts, index) {
- if (index === null || index === undefined)
- return null;
- ngDevMode && assertIndexInRange(consts, index);
- return consts[index];
- }
- /**
- * Resets the pre-order hook flags of the view.
- * @param lView the LView on which the flags are reset
- */
- function resetPreOrderHookFlags(lView) {
- lView[PREORDER_HOOK_FLAGS] = 0;
- }
- /**
- * Adds the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of
- * parents.
- */
- function markViewForRefresh(lView) {
- if ((lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) === 0) {
- lView[FLAGS] |= 1024 /* LViewFlags.RefreshView */;
- updateViewsToRefresh(lView, 1);
- }
- }
- /**
- * Removes the `RefreshView` flag from the lView and updates DESCENDANT_VIEWS_TO_REFRESH counters of
- * parents.
- */
- function clearViewRefreshFlag(lView) {
- if (lView[FLAGS] & 1024 /* LViewFlags.RefreshView */) {
- lView[FLAGS] &= ~1024 /* LViewFlags.RefreshView */;
- updateViewsToRefresh(lView, -1);
- }
- }
- /**
- * Updates the `DESCENDANT_VIEWS_TO_REFRESH` counter on the parents of the `LView` as well as the
- * parents above that whose
- * 1. counter goes from 0 to 1, indicating that there is a new child that has a view to refresh
- * or
- * 2. counter goes from 1 to 0, indicating there are no more descendant views to refresh
- */
- function updateViewsToRefresh(lView, amount) {
- let parent = lView[PARENT];
- if (parent === null) {
- return;
- }
- parent[DESCENDANT_VIEWS_TO_REFRESH] += amount;
- let viewOrContainer = parent;
- parent = parent[PARENT];
- while (parent !== null &&
- ((amount === 1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 1) ||
- (amount === -1 && viewOrContainer[DESCENDANT_VIEWS_TO_REFRESH] === 0))) {
- parent[DESCENDANT_VIEWS_TO_REFRESH] += amount;
- viewOrContainer = parent;
- parent = parent[PARENT];
- }
- }
- /**
- * Stores a LView-specific destroy callback.
- */
- function storeLViewOnDestroy(lView, onDestroyCallback) {
- if ((lView[FLAGS] & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */) {
- throw new RuntimeError(911 /* RuntimeErrorCode.VIEW_ALREADY_DESTROYED */, ngDevMode && 'View has already been destroyed.');
- }
- if (lView[ON_DESTROY_HOOKS] === null) {
- lView[ON_DESTROY_HOOKS] = [];
- }
- lView[ON_DESTROY_HOOKS].push(onDestroyCallback);
- }
- /**
- * Removes previously registered LView-specific destroy callback.
- */
- function removeLViewOnDestroy(lView, onDestroyCallback) {
- if (lView[ON_DESTROY_HOOKS] === null)
- return;
- const destroyCBIdx = lView[ON_DESTROY_HOOKS].indexOf(onDestroyCallback);
- if (destroyCBIdx !== -1) {
- lView[ON_DESTROY_HOOKS].splice(destroyCBIdx, 1);
- }
- }
- const instructionState = {
- lFrame: createLFrame(null),
- bindingsEnabled: true,
- skipHydrationRootTNode: null,
- };
- /**
- * In this mode, any changes in bindings will throw an ExpressionChangedAfterChecked error.
- *
- * Necessary to support ChangeDetectorRef.checkNoChanges().
- *
- * The `checkNoChanges` function is invoked only in ngDevMode=true and verifies that no unintended
- * changes exist in the change detector or its children.
- */
- let _isInCheckNoChangesMode = false;
- /**
- * Returns true if the instruction state stack is empty.
- *
- * Intended to be called from tests only (tree shaken otherwise).
- */
- function specOnlyIsInstructionStateEmpty() {
- return instructionState.lFrame.parent === null;
- }
- function getElementDepthCount() {
- return instructionState.lFrame.elementDepthCount;
- }
- function increaseElementDepthCount() {
- instructionState.lFrame.elementDepthCount++;
- }
- function decreaseElementDepthCount() {
- instructionState.lFrame.elementDepthCount--;
- }
- function getBindingsEnabled() {
- return instructionState.bindingsEnabled;
- }
- /**
- * Returns true if currently inside a skip hydration block.
- * @returns boolean
- */
- function isInSkipHydrationBlock$1() {
- return instructionState.skipHydrationRootTNode !== null;
- }
- /**
- * Returns true if this is the root TNode of the skip hydration block.
- * @param tNode the current TNode
- * @returns boolean
- */
- function isSkipHydrationRootTNode(tNode) {
- return instructionState.skipHydrationRootTNode === tNode;
- }
- /**
- * Enables directive matching on elements.
- *
- * * Example:
- * ```
- * <my-comp my-directive>
- * Should match component / directive.
- * </my-comp>
- * <div ngNonBindable>
- * <!-- ɵɵdisableBindings() -->
- * <my-comp my-directive>
- * Should not match component / directive because we are in ngNonBindable.
- * </my-comp>
- * <!-- ɵɵenableBindings() -->
- * </div>
- * ```
- *
- * @codeGenApi
- */
- function ɵɵenableBindings() {
- instructionState.bindingsEnabled = true;
- }
- /**
- * Sets a flag to specify that the TNode is in a skip hydration block.
- * @param tNode the current TNode
- */
- function enterSkipHydrationBlock(tNode) {
- instructionState.skipHydrationRootTNode = tNode;
- }
- /**
- * Disables directive matching on element.
- *
- * * Example:
- * ```
- * <my-comp my-directive>
- * Should match component / directive.
- * </my-comp>
- * <div ngNonBindable>
- * <!-- ɵɵdisableBindings() -->
- * <my-comp my-directive>
- * Should not match component / directive because we are in ngNonBindable.
- * </my-comp>
- * <!-- ɵɵenableBindings() -->
- * </div>
- * ```
- *
- * @codeGenApi
- */
- function ɵɵdisableBindings() {
- instructionState.bindingsEnabled = false;
- }
- /**
- * Clears the root skip hydration node when leaving a skip hydration block.
- */
- function leaveSkipHydrationBlock() {
- instructionState.skipHydrationRootTNode = null;
- }
- /**
- * Return the current `LView`.
- */
- function getLView() {
- return instructionState.lFrame.lView;
- }
- /**
- * Return the current `TView`.
- */
- function getTView() {
- return instructionState.lFrame.tView;
- }
- /**
- * Restores `contextViewData` to the given OpaqueViewState instance.
- *
- * Used in conjunction with the getCurrentView() instruction to save a snapshot
- * of the current view and restore it when listeners are invoked. This allows
- * walking the declaration view tree in listeners to get vars from parent views.
- *
- * @param viewToRestore The OpaqueViewState instance to restore.
- * @returns Context of the restored OpaqueViewState instance.
- *
- * @codeGenApi
- */
- function ɵɵrestoreView(viewToRestore) {
- instructionState.lFrame.contextLView = viewToRestore;
- return viewToRestore[CONTEXT];
- }
- /**
- * Clears the view set in `ɵɵrestoreView` from memory. Returns the passed in
- * value so that it can be used as a return value of an instruction.
- *
- * @codeGenApi
- */
- function ɵɵresetView(value) {
- instructionState.lFrame.contextLView = null;
- return value;
- }
- function getCurrentTNode() {
- let currentTNode = getCurrentTNodePlaceholderOk();
- while (currentTNode !== null && currentTNode.type === 64 /* TNodeType.Placeholder */) {
- currentTNode = currentTNode.parent;
- }
- return currentTNode;
- }
- function getCurrentTNodePlaceholderOk() {
- return instructionState.lFrame.currentTNode;
- }
- function getCurrentParentTNode() {
- const lFrame = instructionState.lFrame;
- const currentTNode = lFrame.currentTNode;
- return lFrame.isParent ? currentTNode : currentTNode.parent;
- }
- function setCurrentTNode(tNode, isParent) {
- ngDevMode && tNode && assertTNodeForTView(tNode, instructionState.lFrame.tView);
- const lFrame = instructionState.lFrame;
- lFrame.currentTNode = tNode;
- lFrame.isParent = isParent;
- }
- function isCurrentTNodeParent() {
- return instructionState.lFrame.isParent;
- }
- function setCurrentTNodeAsNotParent() {
- instructionState.lFrame.isParent = false;
- }
- function getContextLView() {
- const contextLView = instructionState.lFrame.contextLView;
- ngDevMode && assertDefined(contextLView, 'contextLView must be defined.');
- return contextLView;
- }
- function isInCheckNoChangesMode() {
- !ngDevMode && throwError('Must never be called in production mode');
- return _isInCheckNoChangesMode;
- }
- function setIsInCheckNoChangesMode(mode) {
- !ngDevMode && throwError('Must never be called in production mode');
- _isInCheckNoChangesMode = mode;
- }
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- function getBindingRoot() {
- const lFrame = instructionState.lFrame;
- let index = lFrame.bindingRootIndex;
- if (index === -1) {
- index = lFrame.bindingRootIndex = lFrame.tView.bindingStartIndex;
- }
- return index;
- }
- function getBindingIndex() {
- return instructionState.lFrame.bindingIndex;
- }
- function setBindingIndex(value) {
- return instructionState.lFrame.bindingIndex = value;
- }
- function nextBindingIndex() {
- return instructionState.lFrame.bindingIndex++;
- }
- function incrementBindingIndex(count) {
- const lFrame = instructionState.lFrame;
- const index = lFrame.bindingIndex;
- lFrame.bindingIndex = lFrame.bindingIndex + count;
- return index;
- }
- function isInI18nBlock() {
- return instructionState.lFrame.inI18n;
- }
- function setInI18nBlock(isInI18nBlock) {
- instructionState.lFrame.inI18n = isInI18nBlock;
- }
- /**
- * Set a new binding root index so that host template functions can execute.
- *
- * Bindings inside the host template are 0 index. But because we don't know ahead of time
- * how many host bindings we have we can't pre-compute them. For this reason they are all
- * 0 index and we just shift the root so that they match next available location in the LView.
- *
- * @param bindingRootIndex Root index for `hostBindings`
- * @param currentDirectiveIndex `TData[currentDirectiveIndex]` will point to the current directive
- * whose `hostBindings` are being processed.
- */
- function setBindingRootForHostBindings(bindingRootIndex, currentDirectiveIndex) {
- const lFrame = instructionState.lFrame;
- lFrame.bindingIndex = lFrame.bindingRootIndex = bindingRootIndex;
- setCurrentDirectiveIndex(currentDirectiveIndex);
- }
- /**
- * When host binding is executing this points to the directive index.
- * `TView.data[getCurrentDirectiveIndex()]` is `DirectiveDef`
- * `LView[getCurrentDirectiveIndex()]` is directive instance.
- */
- function getCurrentDirectiveIndex() {
- return instructionState.lFrame.currentDirectiveIndex;
- }
- /**
- * Sets an index of a directive whose `hostBindings` are being processed.
- *
- * @param currentDirectiveIndex `TData` index where current directive instance can be found.
- */
- function setCurrentDirectiveIndex(currentDirectiveIndex) {
- instructionState.lFrame.currentDirectiveIndex = currentDirectiveIndex;
- }
- /**
- * Retrieve the current `DirectiveDef` which is active when `hostBindings` instruction is being
- * executed.
- *
- * @param tData Current `TData` where the `DirectiveDef` will be looked up at.
- */
- function getCurrentDirectiveDef(tData) {
- const currentDirectiveIndex = instructionState.lFrame.currentDirectiveIndex;
- return currentDirectiveIndex === -1 ? null : tData[currentDirectiveIndex];
- }
- function getCurrentQueryIndex() {
- return instructionState.lFrame.currentQueryIndex;
- }
- function setCurrentQueryIndex(value) {
- instructionState.lFrame.currentQueryIndex = value;
- }
- /**
- * Returns a `TNode` of the location where the current `LView` is declared at.
- *
- * @param lView an `LView` that we want to find parent `TNode` for.
- */
- function getDeclarationTNode(lView) {
- const tView = lView[TVIEW];
- // Return the declaration parent for embedded views
- if (tView.type === 2 /* TViewType.Embedded */) {
- ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.');
- return tView.declTNode;
- }
- // Components don't have `TView.declTNode` because each instance of component could be
- // inserted in different location, hence `TView.declTNode` is meaningless.
- // Falling back to `T_HOST` in case we cross component boundary.
- if (tView.type === 1 /* TViewType.Component */) {
- return lView[T_HOST];
- }
- // Remaining TNode type is `TViewType.Root` which doesn't have a parent TNode.
- return null;
- }
- /**
- * This is a light weight version of the `enterView` which is needed by the DI system.
- *
- * @param lView `LView` location of the DI context.
- * @param tNode `TNode` for DI context
- * @param flags DI context flags. if `SkipSelf` flag is set than we walk up the declaration
- * tree from `tNode` until we find parent declared `TElementNode`.
- * @returns `true` if we have successfully entered DI associated with `tNode` (or with declared
- * `TNode` if `flags` has `SkipSelf`). Failing to enter DI implies that no associated
- * `NodeInjector` can be found and we should instead use `ModuleInjector`.
- * - If `true` than this call must be fallowed by `leaveDI`
- * - If `false` than this call failed and we should NOT call `leaveDI`
- */
- function enterDI(lView, tNode, flags) {
- ngDevMode && assertLViewOrUndefined(lView);
- if (flags & InjectFlags.SkipSelf) {
- ngDevMode && assertTNodeForTView(tNode, lView[TVIEW]);
- let parentTNode = tNode;
- let parentLView = lView;
- while (true) {
- ngDevMode && assertDefined(parentTNode, 'Parent TNode should be defined');
- parentTNode = parentTNode.parent;
- if (parentTNode === null && !(flags & InjectFlags.Host)) {
- parentTNode = getDeclarationTNode(parentLView);
- if (parentTNode === null)
- break;
- // In this case, a parent exists and is definitely an element. So it will definitely
- // have an existing lView as the declaration view, which is why we can assume it's defined.
- ngDevMode && assertDefined(parentLView, 'Parent LView should be defined');
- parentLView = parentLView[DECLARATION_VIEW];
- // In Ivy there are Comment nodes that correspond to ngIf and NgFor embedded directives
- // We want to skip those and look only at Elements and ElementContainers to ensure
- // we're looking at true parent nodes, and not content or other types.
- if (parentTNode.type & (2 /* TNodeType.Element */ | 8 /* TNodeType.ElementContainer */)) {
- break;
- }
- }
- else {
- break;
- }
- }
- if (parentTNode === null) {
- // If we failed to find a parent TNode this means that we should use module injector.
- return false;
- }
- else {
- tNode = parentTNode;
- lView = parentLView;
- }
- }
- ngDevMode && assertTNodeForLView(tNode, lView);
- const lFrame = instructionState.lFrame = allocLFrame();
- lFrame.currentTNode = tNode;
- lFrame.lView = lView;
- return true;
- }
- /**
- * Swap the current lView with a new lView.
- *
- * For performance reasons we store the lView in the top level of the module.
- * This way we minimize the number of properties to read. Whenever a new view
- * is entered we have to store the lView for later, and when the view is
- * exited the state has to be restored
- *
- * @param newView New lView to become active
- * @returns the previously active lView;
- */
- function enterView(newView) {
- ngDevMode && assertNotEqual(newView[0], newView[1], '????');
- ngDevMode && assertLViewOrUndefined(newView);
- const newLFrame = allocLFrame();
- if (ngDevMode) {
- assertEqual(newLFrame.isParent, true, 'Expected clean LFrame');
- assertEqual(newLFrame.lView, null, 'Expected clean LFrame');
- assertEqual(newLFrame.tView, null, 'Expected clean LFrame');
- assertEqual(newLFrame.selectedIndex, -1, 'Expected clean LFrame');
- assertEqual(newLFrame.elementDepthCount, 0, 'Expected clean LFrame');
- assertEqual(newLFrame.currentDirectiveIndex, -1, 'Expected clean LFrame');
- assertEqual(newLFrame.currentNamespace, null, 'Expected clean LFrame');
- assertEqual(newLFrame.bindingRootIndex, -1, 'Expected clean LFrame');
- assertEqual(newLFrame.currentQueryIndex, 0, 'Expected clean LFrame');
- }
- const tView = newView[TVIEW];
- instructionState.lFrame = newLFrame;
- ngDevMode && tView.firstChild && assertTNodeForTView(tView.firstChild, tView);
- newLFrame.currentTNode = tView.firstChild;
- newLFrame.lView = newView;
- newLFrame.tView = tView;
- newLFrame.contextLView = newView;
- newLFrame.bindingIndex = tView.bindingStartIndex;
- newLFrame.inI18n = false;
- }
- /**
- * Allocates next free LFrame. This function tries to reuse the `LFrame`s to lower memory pressure.
- */
- function allocLFrame() {
- const currentLFrame = instructionState.lFrame;
- const childLFrame = currentLFrame === null ? null : currentLFrame.child;
- const newLFrame = childLFrame === null ? createLFrame(currentLFrame) : childLFrame;
- return newLFrame;
- }
- function createLFrame(parent) {
- const lFrame = {
- currentTNode: null,
- isParent: true,
- lView: null,
- tView: null,
- selectedIndex: -1,
- contextLView: null,
- elementDepthCount: 0,
- currentNamespace: null,
- currentDirectiveIndex: -1,
- bindingRootIndex: -1,
- bindingIndex: -1,
- currentQueryIndex: 0,
- parent: parent,
- child: null,
- inI18n: false,
- };
- parent !== null && (parent.child = lFrame); // link the new LFrame for reuse.
- return lFrame;
- }
- /**
- * A lightweight version of leave which is used with DI.
- *
- * This function only resets `currentTNode` and `LView` as those are the only properties
- * used with DI (`enterDI()`).
- *
- * NOTE: This function is reexported as `leaveDI`. However `leaveDI` has return type of `void` where
- * as `leaveViewLight` has `LFrame`. This is so that `leaveViewLight` can be used in `leaveView`.
- */
- function leaveViewLight() {
- const oldLFrame = instructionState.lFrame;
- instructionState.lFrame = oldLFrame.parent;
- oldLFrame.currentTNode = null;
- oldLFrame.lView = null;
- return oldLFrame;
- }
- /**
- * This is a lightweight version of the `leaveView` which is needed by the DI system.
- *
- * NOTE: this function is an alias so that we can change the type of the function to have `void`
- * return type.
- */
- const leaveDI = leaveViewLight;
- /**
- * Leave the current `LView`
- *
- * This pops the `LFrame` with the associated `LView` from the stack.
- *
- * IMPORTANT: We must zero out the `LFrame` values here otherwise they will be retained. This is
- * because for performance reasons we don't release `LFrame` but rather keep it for next use.
- */
- function leaveView() {
- const oldLFrame = leaveViewLight();
- oldLFrame.isParent = true;
- oldLFrame.tView = null;
- oldLFrame.selectedIndex = -1;
- oldLFrame.contextLView = null;
- oldLFrame.elementDepthCount = 0;
- oldLFrame.currentDirectiveIndex = -1;
- oldLFrame.currentNamespace = null;
- oldLFrame.bindingRootIndex = -1;
- oldLFrame.bindingIndex = -1;
- oldLFrame.currentQueryIndex = 0;
- }
- function nextContextImpl(level) {
- const contextLView = instructionState.lFrame.contextLView =
- walkUpViews(level, instructionState.lFrame.contextLView);
- return contextLView[CONTEXT];
- }
- function walkUpViews(nestingLevel, currentView) {
- while (nestingLevel > 0) {
- ngDevMode &&
- assertDefined(currentView[DECLARATION_VIEW], 'Declaration view should be defined if nesting level is greater than 0.');
- currentView = currentView[DECLARATION_VIEW];
- nestingLevel--;
- }
- return currentView;
- }
- /**
- * Gets the currently selected element index.
- *
- * Used with {@link property} instruction (and more in the future) to identify the index in the
- * current `LView` to act on.
- */
- function getSelectedIndex() {
- return instructionState.lFrame.selectedIndex;
- }
- /**
- * Sets the most recent index passed to {@link select}
- *
- * Used with {@link property} instruction (and more in the future) to identify the index in the
- * current `LView` to act on.
- *
- * (Note that if an "exit function" was set earlier (via `setElementExitFn()`) then that will be
- * run if and when the provided `index` value is different from the current selected index value.)
- */
- function setSelectedIndex(index) {
- ngDevMode && index !== -1 &&
- assertGreaterThanOrEqual(index, HEADER_OFFSET, 'Index must be past HEADER_OFFSET (or -1).');
- ngDevMode &&
- assertLessThan(index, instructionState.lFrame.lView.length, 'Can\'t set index passed end of LView');
- instructionState.lFrame.selectedIndex = index;
- }
- /**
- * Gets the `tNode` that represents currently selected element.
- */
- function getSelectedTNode() {
- const lFrame = instructionState.lFrame;
- return getTNode(lFrame.tView, lFrame.selectedIndex);
- }
- /**
- * Sets the namespace used to create elements to `'http://www.w3.org/2000/svg'` in global state.
- *
- * @codeGenApi
- */
- function ɵɵnamespaceSVG() {
- instructionState.lFrame.currentNamespace = SVG_NAMESPACE;
- }
- /**
- * Sets the namespace used to create elements to `'http://www.w3.org/1998/MathML/'` in global state.
- *
- * @codeGenApi
- */
- function ɵɵnamespaceMathML() {
- instructionState.lFrame.currentNamespace = MATH_ML_NAMESPACE;
- }
- /**
- * Sets the namespace used to create elements to `null`, which forces element creation to use
- * `createElement` rather than `createElementNS`.
- *
- * @codeGenApi
- */
- function ɵɵnamespaceHTML() {
- namespaceHTMLInternal();
- }
- /**
- * Sets the namespace used to create elements to `null`, which forces element creation to use
- * `createElement` rather than `createElementNS`.
- */
- function namespaceHTMLInternal() {
- instructionState.lFrame.currentNamespace = null;
- }
- function getNamespace$1() {
- return instructionState.lFrame.currentNamespace;
- }
- let _wasLastNodeCreated = true;
- /**
- * Retrieves a global flag that indicates whether the most recent DOM node
- * was created or hydrated.
- */
- function wasLastNodeCreated() {
- return _wasLastNodeCreated;
- }
- /**
- * Sets a global flag to indicate whether the most recent DOM node
- * was created or hydrated.
- */
- function lastNodeWasCreated(flag) {
- _wasLastNodeCreated = flag;
- }
- /**
- * Adds all directive lifecycle hooks from the given `DirectiveDef` to the given `TView`.
- *
- * Must be run *only* on the first template pass.
- *
- * Sets up the pre-order hooks on the provided `tView`,
- * see {@link HookData} for details about the data structure.
- *
- * @param directiveIndex The index of the directive in LView
- * @param directiveDef The definition containing the hooks to setup in tView
- * @param tView The current TView
- */
- function registerPreOrderHooks(directiveIndex, directiveDef, tView) {
- ngDevMode && assertFirstCreatePass(tView);
- const { ngOnChanges, ngOnInit, ngDoCheck } = directiveDef.type.prototype;
- if (ngOnChanges) {
- const wrappedOnChanges = NgOnChangesFeatureImpl(directiveDef);
- (tView.preOrderHooks ??= []).push(directiveIndex, wrappedOnChanges);
- (tView.preOrderCheckHooks ??= []).push(directiveIndex, wrappedOnChanges);
- }
- if (ngOnInit) {
- (tView.preOrderHooks ??= []).push(0 - directiveIndex, ngOnInit);
- }
- if (ngDoCheck) {
- (tView.preOrderHooks ??= []).push(directiveIndex, ngDoCheck);
- (tView.preOrderCheckHooks ??= []).push(directiveIndex, ngDoCheck);
- }
- }
- /**
- *
- * Loops through the directives on the provided `tNode` and queues hooks to be
- * run that are not initialization hooks.
- *
- * Should be executed during `elementEnd()` and similar to
- * preserve hook execution order. Content, view, and destroy hooks for projected
- * components and directives must be called *before* their hosts.
- *
- * Sets up the content, view, and destroy hooks on the provided `tView`,
- * see {@link HookData} for details about the data structure.
- *
- * NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up
- * separately at `elementStart`.
- *
- * @param tView The current TView
- * @param tNode The TNode whose directives are to be searched for hooks to queue
- */
- function registerPostOrderHooks(tView, tNode) {
- ngDevMode && assertFirstCreatePass(tView);
- // It's necessary to loop through the directives at elementEnd() (rather than processing in
- // directiveCreate) so we can preserve the current hook order. Content, view, and destroy
- // hooks for projected components and directives must be called *before* their hosts.
- for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
- const directiveDef = tView.data[i];
- ngDevMode && assertDefined(directiveDef, 'Expecting DirectiveDef');
- const lifecycleHooks = directiveDef.type.prototype;
- const { ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit, ngAfterViewChecked, ngOnDestroy } = lifecycleHooks;
- if (ngAfterContentInit) {
- (tView.contentHooks ??= []).push(-i, ngAfterContentInit);
- }
- if (ngAfterContentChecked) {
- (tView.contentHooks ??= []).push(i, ngAfterContentChecked);
- (tView.contentCheckHooks ??= []).push(i, ngAfterContentChecked);
- }
- if (ngAfterViewInit) {
- (tView.viewHooks ??= []).push(-i, ngAfterViewInit);
- }
- if (ngAfterViewChecked) {
- (tView.viewHooks ??= []).push(i, ngAfterViewChecked);
- (tView.viewCheckHooks ??= []).push(i, ngAfterViewChecked);
- }
- if (ngOnDestroy != null) {
- (tView.destroyHooks ??= []).push(i, ngOnDestroy);
- }
- }
- }
- /**
- * Executing hooks requires complex logic as we need to deal with 2 constraints.
- *
- * 1. Init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) must all be executed once and only
- * once, across many change detection cycles. This must be true even if some hooks throw, or if
- * some recursively trigger a change detection cycle.
- * To solve that, it is required to track the state of the execution of these init hooks.
- * This is done by storing and maintaining flags in the view: the {@link InitPhaseState},
- * and the index within that phase. They can be seen as a cursor in the following structure:
- * [[onInit1, onInit2], [afterContentInit1], [afterViewInit1, afterViewInit2, afterViewInit3]]
- * They are stored as flags in LView[FLAGS].
- *
- * 2. Pre-order hooks can be executed in batches, because of the select instruction.
- * To be able to pause and resume their execution, we also need some state about the hook's array
- * that is being processed:
- * - the index of the next hook to be executed
- * - the number of init hooks already found in the processed part of the array
- * They are stored as flags in LView[PREORDER_HOOK_FLAGS].
- */
- /**
- * Executes pre-order check hooks ( OnChanges, DoChanges) given a view where all the init hooks were
- * executed once. This is a light version of executeInitAndCheckPreOrderHooks where we can skip read
- * / write of the init-hooks related flags.
- * @param lView The LView where hooks are defined
- * @param hooks Hooks to be run
- * @param nodeIndex 3 cases depending on the value:
- * - undefined: all hooks from the array should be executed (post-order case)
- * - null: execute hooks only from the saved index until the end of the array (pre-order case, when
- * flushing the remaining hooks)
- * - number: execute hooks only from the saved index until that node index exclusive (pre-order
- * case, when executing select(number))
- */
- function executeCheckHooks(lView, hooks, nodeIndex) {
- callHooks(lView, hooks, 3 /* InitPhaseState.InitPhaseCompleted */, nodeIndex);
- }
- /**
- * Executes post-order init and check hooks (one of AfterContentInit, AfterContentChecked,
- * AfterViewInit, AfterViewChecked) given a view where there are pending init hooks to be executed.
- * @param lView The LView where hooks are defined
- * @param hooks Hooks to be run
- * @param initPhase A phase for which hooks should be run
- * @param nodeIndex 3 cases depending on the value:
- * - undefined: all hooks from the array should be executed (post-order case)
- * - null: execute hooks only from the saved index until the end of the array (pre-order case, when
- * flushing the remaining hooks)
- * - number: execute hooks only from the saved index until that node index exclusive (pre-order
- * case, when executing select(number))
- */
- function executeInitAndCheckHooks(lView, hooks, initPhase, nodeIndex) {
- ngDevMode &&
- assertNotEqual(initPhase, 3 /* InitPhaseState.InitPhaseCompleted */, 'Init pre-order hooks should not be called more than once');
- if ((lView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) {
- callHooks(lView, hooks, initPhase, nodeIndex);
- }
- }
- function incrementInitPhaseFlags(lView, initPhase) {
- ngDevMode &&
- assertNotEqual(initPhase, 3 /* InitPhaseState.InitPhaseCompleted */, 'Init hooks phase should not be incremented after all init hooks have been run.');
- let flags = lView[FLAGS];
- if ((flags & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) {
- flags &= 8191 /* LViewFlags.IndexWithinInitPhaseReset */;
- flags += 1 /* LViewFlags.InitPhaseStateIncrementer */;
- lView[FLAGS] = flags;
- }
- }
- /**
- * Calls lifecycle hooks with their contexts, skipping init hooks if it's not
- * the first LView pass
- *
- * @param currentView The current view
- * @param arr The array in which the hooks are found
- * @param initPhaseState the current state of the init phase
- * @param currentNodeIndex 3 cases depending on the value:
- * - undefined: all hooks from the array should be executed (post-order case)
- * - null: execute hooks only from the saved index until the end of the array (pre-order case, when
- * flushing the remaining hooks)
- * - number: execute hooks only from the saved index until that node index exclusive (pre-order
- * case, when executing select(number))
- */
- function callHooks(currentView, arr, initPhase, currentNodeIndex) {
- ngDevMode &&
- assertEqual(isInCheckNoChangesMode(), false, 'Hooks should never be run when in check no changes mode.');
- const startIndex = currentNodeIndex !== undefined ?
- (currentView[PREORDER_HOOK_FLAGS] & 65535 /* PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask */) :
- 0;
- const nodeIndexLimit = currentNodeIndex != null ? currentNodeIndex : -1;
- const max = arr.length - 1; // Stop the loop at length - 1, because we look for the hook at i + 1
- let lastNodeIndexFound = 0;
- for (let i = startIndex; i < max; i++) {
- const hook = arr[i + 1];
- if (typeof hook === 'number') {
- lastNodeIndexFound = arr[i];
- if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) {
- break;
- }
- }
- else {
- const isInitHook = arr[i] < 0;
- if (isInitHook) {
- currentView[PREORDER_HOOK_FLAGS] += 65536 /* PreOrderHookFlags.NumberOfInitHooksCalledIncrementer */;
- }
- if (lastNodeIndexFound < nodeIndexLimit || nodeIndexLimit == -1) {
- callHook(currentView, initPhase, arr, i);
- currentView[PREORDER_HOOK_FLAGS] =
- (currentView[PREORDER_HOOK_FLAGS] & 4294901760 /* PreOrderHookFlags.NumberOfInitHooksCalledMask */) + i +
- 2;
- }
- i++;
- }
- }
- }
- /**
- * Executes a single lifecycle hook, making sure that:
- * - it is called in the non-reactive context;
- * - profiling data are registered.
- */
- function callHookInternal(directive, hook) {
- profiler(4 /* ProfilerEvent.LifecycleHookStart */, directive, hook);
- const prevConsumer = setActiveConsumer(null);
- try {
- hook.call(directive);
- }
- finally {
- setActiveConsumer(prevConsumer);
- profiler(5 /* ProfilerEvent.LifecycleHookEnd */, directive, hook);
- }
- }
- /**
- * Execute one hook against the current `LView`.
- *
- * @param currentView The current view
- * @param initPhaseState the current state of the init phase
- * @param arr The array in which the hooks are found
- * @param i The current index within the hook data array
- */
- function callHook(currentView, initPhase, arr, i) {
- const isInitHook = arr[i] < 0;
- const hook = arr[i + 1];
- const directiveIndex = isInitHook ? -arr[i] : arr[i];
- const directive = currentView[directiveIndex];
- if (isInitHook) {
- const indexWithintInitPhase = currentView[FLAGS] >> 13 /* LViewFlags.IndexWithinInitPhaseShift */;
- // The init phase state must be always checked here as it may have been recursively updated.
- if (indexWithintInitPhase <
- (currentView[PREORDER_HOOK_FLAGS] >> 16 /* PreOrderHookFlags.NumberOfInitHooksCalledShift */) &&
- (currentView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === initPhase) {
- currentView[FLAGS] += 8192 /* LViewFlags.IndexWithinInitPhaseIncrementer */;
- callHookInternal(directive, hook);
- }
- }
- else {
- callHookInternal(directive, hook);
- }
- }
- const NO_PARENT_INJECTOR = -1;
- /**
- * Each injector is saved in 9 contiguous slots in `LView` and 9 contiguous slots in
- * `TView.data`. This allows us to store information about the current node's tokens (which
- * can be shared in `TView`) as well as the tokens of its ancestor nodes (which cannot be
- * shared, so they live in `LView`).
- *
- * Each of these slots (aside from the last slot) contains a bloom filter. This bloom filter
- * determines whether a directive is available on the associated node or not. This prevents us
- * from searching the directives array at this level unless it's probable the directive is in it.
- *
- * See: https://en.wikipedia.org/wiki/Bloom_filter for more about bloom filters.
- *
- * Because all injectors have been flattened into `LView` and `TViewData`, they cannot typed
- * using interfaces as they were previously. The start index of each `LInjector` and `TInjector`
- * will differ based on where it is flattened into the main array, so it's not possible to know
- * the indices ahead of time and save their types here. The interfaces are still included here
- * for documentation purposes.
- *
- * export interface LInjector extends Array<any> {
- *
- * // Cumulative bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE)
- * [0]: number;
- *
- * // Cumulative bloom for directive IDs 32-63
- * [1]: number;
- *
- * // Cumulative bloom for directive IDs 64-95
- * [2]: number;
- *
- * // Cumulative bloom for directive IDs 96-127
- * [3]: number;
- *
- * // Cumulative bloom for directive IDs 128-159
- * [4]: number;
- *
- * // Cumulative bloom for directive IDs 160 - 191
- * [5]: number;
- *
- * // Cumulative bloom for directive IDs 192 - 223
- * [6]: number;
- *
- * // Cumulative bloom for directive IDs 224 - 255
- * [7]: number;
- *
- * // We need to store a reference to the injector's parent so DI can keep looking up
- * // the injector tree until it finds the dependency it's looking for.
- * [PARENT_INJECTOR]: number;
- * }
- *
- * export interface TInjector extends Array<any> {
- *
- * // Shared node bloom for directive IDs 0-31 (IDs are % BLOOM_SIZE)
- * [0]: number;
- *
- * // Shared node bloom for directive IDs 32-63
- * [1]: number;
- *
- * // Shared node bloom for directive IDs 64-95
- * [2]: number;
- *
- * // Shared node bloom for directive IDs 96-127
- * [3]: number;
- *
- * // Shared node bloom for directive IDs 128-159
- * [4]: number;
- *
- * // Shared node bloom for directive IDs 160 - 191
- * [5]: number;
- *
- * // Shared node bloom for directive IDs 192 - 223
- * [6]: number;
- *
- * // Shared node bloom for directive IDs 224 - 255
- * [7]: number;
- *
- * // Necessary to find directive indices for a particular node.
- * [TNODE]: TElementNode|TElementContainerNode|TContainerNode;
- * }
- */
- /**
- * Factory for creating instances of injectors in the NodeInjector.
- *
- * This factory is complicated by the fact that it can resolve `multi` factories as well.
- *
- * NOTE: Some of the fields are optional which means that this class has two hidden classes.
- * - One without `multi` support (most common)
- * - One with `multi` values, (rare).
- *
- * Since VMs can cache up to 4 inline hidden classes this is OK.
- *
- * - Single factory: Only `resolving` and `factory` is defined.
- * - `providers` factory: `componentProviders` is a number and `index = -1`.
- * - `viewProviders` factory: `componentProviders` is a number and `index` points to `providers`.
- */
- class NodeInjectorFactory {
- constructor(
- /**
- * Factory to invoke in order to create a new instance.
- */
- factory,
- /**
- * Set to `true` if the token is declared in `viewProviders` (or if it is component).
- */
- isViewProvider, injectImplementation) {
- this.factory = factory;
- /**
- * Marker set to true during factory invocation to see if we get into recursive loop.
- * Recursive loop causes an error to be displayed.
- */
- this.resolving = false;
- ngDevMode && assertDefined(factory, 'Factory not specified');
- ngDevMode && assertEqual(typeof factory, 'function', 'Expected factory function.');
- this.canSeeViewProviders = isViewProvider;
- this.injectImpl = injectImplementation;
- }
- }
- function isFactory(obj) {
- return obj instanceof NodeInjectorFactory;
- }
- // Note: This hack is necessary so we don't erroneously get a circular dependency
- // failure based on types.
- const unusedValueExportToPlacateAjd$2 = 1;
- /**
- * Converts `TNodeType` into human readable text.
- * Make sure this matches with `TNodeType`
- */
- function toTNodeTypeAsString(tNodeType) {
- let text = '';
- (tNodeType & 1 /* TNodeType.Text */) && (text += '|Text');
- (tNodeType & 2 /* TNodeType.Element */) && (text += '|Element');
- (tNodeType & 4 /* TNodeType.Container */) && (text += '|Container');
- (tNodeType & 8 /* TNodeType.ElementContainer */) && (text += '|ElementContainer');
- (tNodeType & 16 /* TNodeType.Projection */) && (text += '|Projection');
- (tNodeType & 32 /* TNodeType.Icu */) && (text += '|IcuContainer');
- (tNodeType & 64 /* TNodeType.Placeholder */) && (text += '|Placeholder');
- return text.length > 0 ? text.substring(1) : text;
- }
- // Note: This hack is necessary so we don't erroneously get a circular dependency
- // failure based on types.
- const unusedValueExportToPlacateAjd$1 = 1;
- /**
- * Returns `true` if the `TNode` has a directive which has `@Input()` for `class` binding.
- *
- * ```
- * <div my-dir [class]="exp"></div>
- * ```
- * and
- * ```
- * @Directive({
- * })
- * class MyDirective {
- * @Input()
- * class: string;
- * }
- * ```
- *
- * In the above case it is necessary to write the reconciled styling information into the
- * directive's input.
- *
- * @param tNode
- */
- function hasClassInput(tNode) {
- return (tNode.flags & 8 /* TNodeFlags.hasClassInput */) !== 0;
- }
- /**
- * Returns `true` if the `TNode` has a directive which has `@Input()` for `style` binding.
- *
- * ```
- * <div my-dir [style]="exp"></div>
- * ```
- * and
- * ```
- * @Directive({
- * })
- * class MyDirective {
- * @Input()
- * class: string;
- * }
- * ```
- *
- * In the above case it is necessary to write the reconciled styling information into the
- * directive's input.
- *
- * @param tNode
- */
- function hasStyleInput(tNode) {
- return (tNode.flags & 16 /* TNodeFlags.hasStyleInput */) !== 0;
- }
- function assertTNodeType(tNode, expectedTypes, message) {
- assertDefined(tNode, 'should be called with a TNode');
- if ((tNode.type & expectedTypes) === 0) {
- throwError(message ||
- `Expected [${toTNodeTypeAsString(expectedTypes)}] but got ${toTNodeTypeAsString(tNode.type)}.`);
- }
- }
- function assertPureTNodeType(type) {
- if (!(type === 2 /* TNodeType.Element */ || //
- type === 1 /* TNodeType.Text */ || //
- type === 4 /* TNodeType.Container */ || //
- type === 8 /* TNodeType.ElementContainer */ || //
- type === 32 /* TNodeType.Icu */ || //
- type === 16 /* TNodeType.Projection */ || //
- type === 64 /* TNodeType.Placeholder */)) {
- throwError(`Expected TNodeType to have only a single type selected, but got ${toTNodeTypeAsString(type)}.`);
- }
- }
- /// Parent Injector Utils ///////////////////////////////////////////////////////////////
- function hasParentInjector(parentLocation) {
- return parentLocation !== NO_PARENT_INJECTOR;
- }
- function getParentInjectorIndex(parentLocation) {
- ngDevMode && assertNumber(parentLocation, 'Number expected');
- ngDevMode && assertNotEqual(parentLocation, -1, 'Not a valid state.');
- const parentInjectorIndex = parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */;
- ngDevMode &&
- assertGreaterThan(parentInjectorIndex, HEADER_OFFSET, 'Parent injector must be pointing past HEADER_OFFSET.');
- return parentLocation & 32767 /* RelativeInjectorLocationFlags.InjectorIndexMask */;
- }
- function getParentInjectorViewOffset(parentLocation) {
- return parentLocation >> 16 /* RelativeInjectorLocationFlags.ViewOffsetShift */;
- }
- /**
- * Unwraps a parent injector location number to find the view offset from the current injector,
- * then walks up the declaration view tree until the view is found that contains the parent
- * injector.
- *
- * @param location The location of the parent injector, which contains the view offset
- * @param startView The LView instance from which to start walking up the view tree
- * @returns The LView instance that contains the parent injector
- */
- function getParentInjectorView(location, startView) {
- let viewOffset = getParentInjectorViewOffset(location);
- let parentView = startView;
- // For most cases, the parent injector can be found on the host node (e.g. for component
- // or container), but we must keep the loop here to support the rarer case of deeply nested
- // <ng-template> tags or inline views, where the parent injector might live many views
- // above the child injector.
- while (viewOffset > 0) {
- parentView = parentView[DECLARATION_VIEW];
- viewOffset--;
- }
- return parentView;
- }
- /**
- * Defines if the call to `inject` should include `viewProviders` in its resolution.
- *
- * This is set to true when we try to instantiate a component. This value is reset in
- * `getNodeInjectable` to a value which matches the declaration location of the token about to be
- * instantiated. This is done so that if we are injecting a token which was declared outside of
- * `viewProviders` we don't accidentally pull `viewProviders` in.
- *
- * Example:
- *
- * ```
- * @Injectable()
- * class MyService {
- * constructor(public value: String) {}
- * }
- *
- * @Component({
- * providers: [
- * MyService,
- * {provide: String, value: 'providers' }
- * ]
- * viewProviders: [
- * {provide: String, value: 'viewProviders'}
- * ]
- * })
- * class MyComponent {
- * constructor(myService: MyService, value: String) {
- * // We expect that Component can see into `viewProviders`.
- * expect(value).toEqual('viewProviders');
- * // `MyService` was not declared in `viewProviders` hence it can't see it.
- * expect(myService.value).toEqual('providers');
- * }
- * }
- *
- * ```
- */
- let includeViewProviders = true;
- function setIncludeViewProviders(v) {
- const oldValue = includeViewProviders;
- includeViewProviders = v;
- return oldValue;
- }
- /**
- * The number of slots in each bloom filter (used by DI). The larger this number, the fewer
- * directives that will share slots, and thus, the fewer false positives when checking for
- * the existence of a directive.
- */
- const BLOOM_SIZE = 256;
- const BLOOM_MASK = BLOOM_SIZE - 1;
- /**
- * The number of bits that is represented by a single bloom bucket. JS bit operations are 32 bits,
- * so each bucket represents 32 distinct tokens which accounts for log2(32) = 5 bits of a bloom hash
- * number.
- */
- const BLOOM_BUCKET_BITS = 5;
- /** Counter used to generate unique IDs for directives. */
- let nextNgElementId = 0;
- /** Value used when something wasn't found by an injector. */
- const NOT_FOUND = {};
- /**
- * Registers this directive as present in its node's injector by flipping the directive's
- * corresponding bit in the injector's bloom filter.
- *
- * @param injectorIndex The index of the node injector where this token should be registered
- * @param tView The TView for the injector's bloom filters
- * @param type The directive token to register
- */
- function bloomAdd(injectorIndex, tView, type) {
- ngDevMode && assertEqual(tView.firstCreatePass, true, 'expected firstCreatePass to be true');
- let id;
- if (typeof type === 'string') {
- id = type.charCodeAt(0) || 0;
- }
- else if (type.hasOwnProperty(NG_ELEMENT_ID)) {
- id = type[NG_ELEMENT_ID];
- }
- // Set a unique ID on the directive type, so if something tries to inject the directive,
- // we can easily retrieve the ID and hash it into the bloom bit that should be checked.
- if (id == null) {
- id = type[NG_ELEMENT_ID] = nextNgElementId++;
- }
- // We only have BLOOM_SIZE (256) slots in our bloom filter (8 buckets * 32 bits each),
- // so all unique IDs must be modulo-ed into a number from 0 - 255 to fit into the filter.
- const bloomHash = id & BLOOM_MASK;
- // Create a mask that targets the specific bit associated with the directive.
- // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
- // to bit positions 0 - 31 in a 32 bit integer.
- const mask = 1 << bloomHash;
- // Each bloom bucket in `tData` represents `BLOOM_BUCKET_BITS` number of bits of `bloomHash`.
- // Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset that the mask
- // should be written to.
- tView.data[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)] |= mask;
- }
- /**
- * Creates (or gets an existing) injector for a given element or container.
- *
- * @param tNode for which an injector should be retrieved / created.
- * @param lView View where the node is stored
- * @returns Node injector
- */
- function getOrCreateNodeInjectorForNode(tNode, lView) {
- const existingInjectorIndex = getInjectorIndex(tNode, lView);
- if (existingInjectorIndex !== -1) {
- return existingInjectorIndex;
- }
- const tView = lView[TVIEW];
- if (tView.firstCreatePass) {
- tNode.injectorIndex = lView.length;
- insertBloom(tView.data, tNode); // foundation for node bloom
- insertBloom(lView, null); // foundation for cumulative bloom
- insertBloom(tView.blueprint, null);
- }
- const parentLoc = getParentInjectorLocation(tNode, lView);
- const injectorIndex = tNode.injectorIndex;
- // If a parent injector can't be found, its location is set to -1.
- // In that case, we don't need to set up a cumulative bloom
- if (hasParentInjector(parentLoc)) {
- const parentIndex = getParentInjectorIndex(parentLoc);
- const parentLView = getParentInjectorView(parentLoc, lView);
- const parentData = parentLView[TVIEW].data;
- // Creates a cumulative bloom filter that merges the parent's bloom filter
- // and its own cumulative bloom (which contains tokens for all ancestors)
- for (let i = 0; i < 8 /* NodeInjectorOffset.BLOOM_SIZE */; i++) {
- lView[injectorIndex + i] = parentLView[parentIndex + i] | parentData[parentIndex + i];
- }
- }
- lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */] = parentLoc;
- return injectorIndex;
- }
- function insertBloom(arr, footer) {
- arr.push(0, 0, 0, 0, 0, 0, 0, 0, footer);
- }
- function getInjectorIndex(tNode, lView) {
- if (tNode.injectorIndex === -1 ||
- // If the injector index is the same as its parent's injector index, then the index has been
- // copied down from the parent node. No injector has been created yet on this node.
- (tNode.parent && tNode.parent.injectorIndex === tNode.injectorIndex) ||
- // After the first template pass, the injector index might exist but the parent values
- // might not have been calculated yet for this instance
- lView[tNode.injectorIndex + 8 /* NodeInjectorOffset.PARENT */] === null) {
- return -1;
- }
- else {
- ngDevMode && assertIndexInRange(lView, tNode.injectorIndex);
- return tNode.injectorIndex;
- }
- }
- /**
- * Finds the index of the parent injector, with a view offset if applicable. Used to set the
- * parent injector initially.
- *
- * @returns Returns a number that is the combination of the number of LViews that we have to go up
- * to find the LView containing the parent inject AND the index of the injector within that LView.
- */
- function getParentInjectorLocation(tNode, lView) {
- if (tNode.parent && tNode.parent.injectorIndex !== -1) {
- // If we have a parent `TNode` and there is an injector associated with it we are done, because
- // the parent injector is within the current `LView`.
- return tNode.parent.injectorIndex; // ViewOffset is 0
- }
- // When parent injector location is computed it may be outside of the current view. (ie it could
- // be pointing to a declared parent location). This variable stores number of declaration parents
- // we need to walk up in order to find the parent injector location.
- let declarationViewOffset = 0;
- let parentTNode = null;
- let lViewCursor = lView;
- // The parent injector is not in the current `LView`. We will have to walk the declared parent
- // `LView` hierarchy and look for it. If we walk of the top, that means that there is no parent
- // `NodeInjector`.
- while (lViewCursor !== null) {
- parentTNode = getTNodeFromLView(lViewCursor);
- if (parentTNode === null) {
- // If we have no parent, than we are done.
- return NO_PARENT_INJECTOR;
- }
- ngDevMode && parentTNode && assertTNodeForLView(parentTNode, lViewCursor[DECLARATION_VIEW]);
- // Every iteration of the loop requires that we go to the declared parent.
- declarationViewOffset++;
- lViewCursor = lViewCursor[DECLARATION_VIEW];
- if (parentTNode.injectorIndex !== -1) {
- // We found a NodeInjector which points to something.
- return (parentTNode.injectorIndex |
- (declarationViewOffset << 16 /* RelativeInjectorLocationFlags.ViewOffsetShift */));
- }
- }
- return NO_PARENT_INJECTOR;
- }
- /**
- * Makes a type or an injection token public to the DI system by adding it to an
- * injector's bloom filter.
- *
- * @param di The node injector in which a directive will be added
- * @param token The type or the injection token to be made public
- */
- function diPublicInInjector(injectorIndex, tView, token) {
- bloomAdd(injectorIndex, tView, token);
- }
- /**
- * Inject static attribute value into directive constructor.
- *
- * This method is used with `factory` functions which are generated as part of
- * `defineDirective` or `defineComponent`. The method retrieves the static value
- * of an attribute. (Dynamic attributes are not supported since they are not resolved
- * at the time of injection and can change over time.)
- *
- * # Example
- * Given:
- * ```
- * @Component(...)
- * class MyComponent {
- * constructor(@Attribute('title') title: string) { ... }
- * }
- * ```
- * When instantiated with
- * ```
- * <my-component title="Hello"></my-component>
- * ```
- *
- * Then factory method generated is:
- * ```
- * MyComponent.ɵcmp = defineComponent({
- * factory: () => new MyComponent(injectAttribute('title'))
- * ...
- * })
- * ```
- *
- * @publicApi
- */
- function injectAttributeImpl(tNode, attrNameToInject) {
- ngDevMode && assertTNodeType(tNode, 12 /* TNodeType.AnyContainer */ | 3 /* TNodeType.AnyRNode */);
- ngDevMode && assertDefined(tNode, 'expecting tNode');
- if (attrNameToInject === 'class') {
- return tNode.classes;
- }
- if (attrNameToInject === 'style') {
- return tNode.styles;
- }
- const attrs = tNode.attrs;
- if (attrs) {
- const attrsLength = attrs.length;
- let i = 0;
- while (i < attrsLength) {
- const value = attrs[i];
- // If we hit a `Bindings` or `Template` marker then we are done.
- if (isNameOnlyAttributeMarker(value))
- break;
- // Skip namespaced attributes
- if (value === 0 /* AttributeMarker.NamespaceURI */) {
- // we skip the next two values
- // as namespaced attributes looks like
- // [..., AttributeMarker.NamespaceURI, 'http://someuri.com/test', 'test:exist',
- // 'existValue', ...]
- i = i + 2;
- }
- else if (typeof value === 'number') {
- // Skip to the first value of the marked attribute.
- i++;
- while (i < attrsLength && typeof attrs[i] === 'string') {
- i++;
- }
- }
- else if (value === attrNameToInject) {
- return attrs[i + 1];
- }
- else {
- i = i + 2;
- }
- }
- }
- return null;
- }
- function notFoundValueOrThrow(notFoundValue, token, flags) {
- if ((flags & InjectFlags.Optional) || notFoundValue !== undefined) {
- return notFoundValue;
- }
- else {
- throwProviderNotFoundError(token, 'NodeInjector');
- }
- }
- /**
- * Returns the value associated to the given token from the ModuleInjector or throws exception
- *
- * @param lView The `LView` that contains the `tNode`
- * @param token The token to look for
- * @param flags Injection flags
- * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
- * @returns the value from the injector or throws an exception
- */
- function lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue) {
- if ((flags & InjectFlags.Optional) && notFoundValue === undefined) {
- // This must be set or the NullInjector will throw for optional deps
- notFoundValue = null;
- }
- if ((flags & (InjectFlags.Self | InjectFlags.Host)) === 0) {
- const moduleInjector = lView[INJECTOR$1];
- // switch to `injectInjectorOnly` implementation for module injector, since module injector
- // should not have access to Component/Directive DI scope (that may happen through
- // `directiveInject` implementation)
- const previousInjectImplementation = setInjectImplementation(undefined);
- try {
- if (moduleInjector) {
- return moduleInjector.get(token, notFoundValue, flags & InjectFlags.Optional);
- }
- else {
- return injectRootLimpMode(token, notFoundValue, flags & InjectFlags.Optional);
- }
- }
- finally {
- setInjectImplementation(previousInjectImplementation);
- }
- }
- return notFoundValueOrThrow(notFoundValue, token, flags);
- }
- /**
- * Returns the value associated to the given token from the NodeInjectors => ModuleInjector.
- *
- * Look for the injector providing the token by walking up the node injector tree and then
- * the module injector tree.
- *
- * This function patches `token` with `__NG_ELEMENT_ID__` which contains the id for the bloom
- * filter. `-1` is reserved for injecting `Injector` (implemented by `NodeInjector`)
- *
- * @param tNode The Node where the search for the injector should start
- * @param lView The `LView` that contains the `tNode`
- * @param token The token to look for
- * @param flags Injection flags
- * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
- * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
- */
- function getOrCreateInjectable(tNode, lView, token, flags = InjectFlags.Default, notFoundValue) {
- if (tNode !== null) {
- // If the view or any of its ancestors have an embedded
- // view injector, we have to look it up there first.
- if (lView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */ &&
- // The token must be present on the current node injector when the `Self`
- // flag is set, so the lookup on embedded view injector(s) can be skipped.
- !(flags & InjectFlags.Self)) {
- const embeddedInjectorValue = lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, NOT_FOUND);
- if (embeddedInjectorValue !== NOT_FOUND) {
- return embeddedInjectorValue;
- }
- }
- // Otherwise try the node injector.
- const value = lookupTokenUsingNodeInjector(tNode, lView, token, flags, NOT_FOUND);
- if (value !== NOT_FOUND) {
- return value;
- }
- }
- // Finally, fall back to the module injector.
- return lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue);
- }
- /**
- * Returns the value associated to the given token from the node injector.
- *
- * @param tNode The Node where the search for the injector should start
- * @param lView The `LView` that contains the `tNode`
- * @param token The token to look for
- * @param flags Injection flags
- * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
- * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
- */
- function lookupTokenUsingNodeInjector(tNode, lView, token, flags, notFoundValue) {
- const bloomHash = bloomHashBitOrFactory(token);
- // If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
- // so just call the factory function to create it.
- if (typeof bloomHash === 'function') {
- if (!enterDI(lView, tNode, flags)) {
- // Failed to enter DI, try module injector instead. If a token is injected with the @Host
- // flag, the module injector is not searched for that token in Ivy.
- return (flags & InjectFlags.Host) ?
- notFoundValueOrThrow(notFoundValue, token, flags) :
- lookupTokenUsingModuleInjector(lView, token, flags, notFoundValue);
- }
- try {
- let value;
- if (ngDevMode) {
- runInInjectorProfilerContext(new NodeInjector(getCurrentTNode(), getLView()), token, () => {
- value = bloomHash(flags);
- if (value != null) {
- emitInstanceCreatedByInjectorEvent(value);
- }
- });
- }
- else {
- value = bloomHash(flags);
- }
- if (value == null && !(flags & InjectFlags.Optional)) {
- throwProviderNotFoundError(token);
- }
- else {
- return value;
- }
- }
- finally {
- leaveDI();
- }
- }
- else if (typeof bloomHash === 'number') {
- // A reference to the previous injector TView that was found while climbing the element
- // injector tree. This is used to know if viewProviders can be accessed on the current
- // injector.
- let previousTView = null;
- let injectorIndex = getInjectorIndex(tNode, lView);
- let parentLocation = NO_PARENT_INJECTOR;
- let hostTElementNode = flags & InjectFlags.Host ? lView[DECLARATION_COMPONENT_VIEW][T_HOST] : null;
- // If we should skip this injector, or if there is no injector on this node, start by
- // searching the parent injector.
- if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
- parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
- lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */];
- if (parentLocation === NO_PARENT_INJECTOR || !shouldSearchParent(flags, false)) {
- injectorIndex = -1;
- }
- else {
- previousTView = lView[TVIEW];
- injectorIndex = getParentInjectorIndex(parentLocation);
- lView = getParentInjectorView(parentLocation, lView);
- }
- }
- // Traverse up the injector tree until we find a potential match or until we know there
- // *isn't* a match.
- while (injectorIndex !== -1) {
- ngDevMode && assertNodeInjector(lView, injectorIndex);
- // Check the current injector. If it matches, see if it contains token.
- const tView = lView[TVIEW];
- ngDevMode &&
- assertTNodeForLView(tView.data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */], lView);
- if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
- // At this point, we have an injector which *may* contain the token, so we step through
- // the providers and directives associated with the injector's corresponding node to get
- // the instance.
- const instance = searchTokensOnInjector(injectorIndex, lView, token, previousTView, flags, hostTElementNode);
- if (instance !== NOT_FOUND) {
- return instance;
- }
- }
- parentLocation = lView[injectorIndex + 8 /* NodeInjectorOffset.PARENT */];
- if (parentLocation !== NO_PARENT_INJECTOR &&
- shouldSearchParent(flags, lView[TVIEW].data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */] === hostTElementNode) &&
- bloomHasToken(bloomHash, injectorIndex, lView)) {
- // The def wasn't found anywhere on this node, so it was a false positive.
- // Traverse up the tree and continue searching.
- previousTView = tView;
- injectorIndex = getParentInjectorIndex(parentLocation);
- lView = getParentInjectorView(parentLocation, lView);
- }
- else {
- // If we should not search parent OR If the ancestor bloom filter value does not have the
- // bit corresponding to the directive we can give up on traversing up to find the specific
- // injector.
- injectorIndex = -1;
- }
- }
- }
- return notFoundValue;
- }
- function searchTokensOnInjector(injectorIndex, lView, token, previousTView, flags, hostTElementNode) {
- const currentTView = lView[TVIEW];
- const tNode = currentTView.data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */];
- // First, we need to determine if view providers can be accessed by the starting element.
- // There are two possibilities
- const canAccessViewProviders = previousTView == null ?
- // 1) This is the first invocation `previousTView == null` which means that we are at the
- // `TNode` of where injector is starting to look. In such a case the only time we are allowed
- // to look into the ViewProviders is if:
- // - we are on a component
- // - AND the injector set `includeViewProviders` to true (implying that the token can see
- // ViewProviders because it is the Component or a Service which itself was declared in
- // ViewProviders)
- (isComponentHost(tNode) && includeViewProviders) :
- // 2) `previousTView != null` which means that we are now walking across the parent nodes.
- // In such a case we are only allowed to look into the ViewProviders if:
- // - We just crossed from child View to Parent View `previousTView != currentTView`
- // - AND the parent TNode is an Element.
- // This means that we just came from the Component's View and therefore are allowed to see
- // into the ViewProviders.
- (previousTView != currentTView && ((tNode.type & 3 /* TNodeType.AnyRNode */) !== 0));
- // This special case happens when there is a @host on the inject and when we are searching
- // on the host element node.
- const isHostSpecialCase = (flags & InjectFlags.Host) && hostTElementNode === tNode;
- const injectableIdx = locateDirectiveOrProvider(tNode, currentTView, token, canAccessViewProviders, isHostSpecialCase);
- if (injectableIdx !== null) {
- return getNodeInjectable(lView, currentTView, injectableIdx, tNode);
- }
- else {
- return NOT_FOUND;
- }
- }
- /**
- * Searches for the given token among the node's directives and providers.
- *
- * @param tNode TNode on which directives are present.
- * @param tView The tView we are currently processing
- * @param token Provider token or type of a directive to look for.
- * @param canAccessViewProviders Whether view providers should be considered.
- * @param isHostSpecialCase Whether the host special case applies.
- * @returns Index of a found directive or provider, or null when none found.
- */
- function locateDirectiveOrProvider(tNode, tView, token, canAccessViewProviders, isHostSpecialCase) {
- const nodeProviderIndexes = tNode.providerIndexes;
- const tInjectables = tView.data;
- const injectablesStart = nodeProviderIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */;
- const directivesStart = tNode.directiveStart;
- const directiveEnd = tNode.directiveEnd;
- const cptViewProvidersCount = nodeProviderIndexes >> 20 /* TNodeProviderIndexes.CptViewProvidersCountShift */;
- const startingIndex = canAccessViewProviders ? injectablesStart : injectablesStart + cptViewProvidersCount;
- // When the host special case applies, only the viewProviders and the component are visible
- const endIndex = isHostSpecialCase ? injectablesStart + cptViewProvidersCount : directiveEnd;
- for (let i = startingIndex; i < endIndex; i++) {
- const providerTokenOrDef = tInjectables[i];
- if (i < directivesStart && token === providerTokenOrDef ||
- i >= directivesStart && providerTokenOrDef.type === token) {
- return i;
- }
- }
- if (isHostSpecialCase) {
- const dirDef = tInjectables[directivesStart];
- if (dirDef && isComponentDef(dirDef) && dirDef.type === token) {
- return directivesStart;
- }
- }
- return null;
- }
- /**
- * Retrieve or instantiate the injectable from the `LView` at particular `index`.
- *
- * This function checks to see if the value has already been instantiated and if so returns the
- * cached `injectable`. Otherwise if it detects that the value is still a factory it
- * instantiates the `injectable` and caches the value.
- */
- function getNodeInjectable(lView, tView, index, tNode) {
- let value = lView[index];
- const tData = tView.data;
- if (isFactory(value)) {
- const factory = value;
- if (factory.resolving) {
- throwCyclicDependencyError(stringifyForError(tData[index]));
- }
- const previousIncludeViewProviders = setIncludeViewProviders(factory.canSeeViewProviders);
- factory.resolving = true;
- let prevInjectContext;
- if (ngDevMode) {
- // tData indexes mirror the concrete instances in its corresponding LView.
- // lView[index] here is either the injectable instace itself or a factory,
- // therefore tData[index] is the constructor of that injectable or a
- // definition object that contains the constructor in a `.type` field.
- const token = tData[index].type || tData[index];
- const injector = new NodeInjector(tNode, lView);
- prevInjectContext = setInjectorProfilerContext({ injector, token });
- }
- const previousInjectImplementation = factory.injectImpl ? setInjectImplementation(factory.injectImpl) : null;
- const success = enterDI(lView, tNode, InjectFlags.Default);
- ngDevMode &&
- assertEqual(success, true, 'Because flags do not contain \`SkipSelf\' we expect this to always succeed.');
- try {
- value = lView[index] = factory.factory(undefined, tData, lView, tNode);
- ngDevMode && emitInstanceCreatedByInjectorEvent(value);
- // This code path is hit for both directives and providers.
- // For perf reasons, we want to avoid searching for hooks on providers.
- // It does no harm to try (the hooks just won't exist), but the extra
- // checks are unnecessary and this is a hot path. So we check to see
- // if the index of the dependency is in the directive range for this
- // tNode. If it's not, we know it's a provider and skip hook registration.
- if (tView.firstCreatePass && index >= tNode.directiveStart) {
- ngDevMode && assertDirectiveDef(tData[index]);
- registerPreOrderHooks(index, tData[index], tView);
- }
- }
- finally {
- ngDevMode && setInjectorProfilerContext(prevInjectContext);
- previousInjectImplementation !== null &&
- setInjectImplementation(previousInjectImplementation);
- setIncludeViewProviders(previousIncludeViewProviders);
- factory.resolving = false;
- leaveDI();
- }
- }
- return value;
- }
- /**
- * Returns the bit in an injector's bloom filter that should be used to determine whether or not
- * the directive might be provided by the injector.
- *
- * When a directive is public, it is added to the bloom filter and given a unique ID that can be
- * retrieved on the Type. When the directive isn't public or the token is not a directive `null`
- * is returned as the node injector can not possibly provide that token.
- *
- * @param token the injection token
- * @returns the matching bit to check in the bloom filter or `null` if the token is not known.
- * When the returned value is negative then it represents special values such as `Injector`.
- */
- function bloomHashBitOrFactory(token) {
- ngDevMode && assertDefined(token, 'token must be defined');
- if (typeof token === 'string') {
- return token.charCodeAt(0) || 0;
- }
- const tokenId =
- // First check with `hasOwnProperty` so we don't get an inherited ID.
- token.hasOwnProperty(NG_ELEMENT_ID) ? token[NG_ELEMENT_ID] : undefined;
- // Negative token IDs are used for special objects such as `Injector`
- if (typeof tokenId === 'number') {
- if (tokenId >= 0) {
- return tokenId & BLOOM_MASK;
- }
- else {
- ngDevMode &&
- assertEqual(tokenId, -1 /* InjectorMarkers.Injector */, 'Expecting to get Special Injector Id');
- return createNodeInjector;
- }
- }
- else {
- return tokenId;
- }
- }
- function bloomHasToken(bloomHash, injectorIndex, injectorView) {
- // Create a mask that targets the specific bit associated with the directive we're looking for.
- // JS bit operations are 32 bits, so this will be a number between 2^0 and 2^31, corresponding
- // to bit positions 0 - 31 in a 32 bit integer.
- const mask = 1 << bloomHash;
- // Each bloom bucket in `injectorView` represents `BLOOM_BUCKET_BITS` number of bits of
- // `bloomHash`. Any bits in `bloomHash` beyond `BLOOM_BUCKET_BITS` indicate the bucket offset
- // that should be used.
- const value = injectorView[injectorIndex + (bloomHash >> BLOOM_BUCKET_BITS)];
- // If the bloom filter value has the bit corresponding to the directive's bloomBit flipped on,
- // this injector is a potential match.
- return !!(value & mask);
- }
- /** Returns true if flags prevent parent injector from being searched for tokens */
- function shouldSearchParent(flags, isFirstHostTNode) {
- return !(flags & InjectFlags.Self) && !(flags & InjectFlags.Host && isFirstHostTNode);
- }
- function getNodeInjectorLView(nodeInjector) {
- return nodeInjector._lView;
- }
- function getNodeInjectorTNode(nodeInjector) {
- return nodeInjector._tNode;
- }
- class NodeInjector {
- constructor(_tNode, _lView) {
- this._tNode = _tNode;
- this._lView = _lView;
- }
- get(token, notFoundValue, flags) {
- return getOrCreateInjectable(this._tNode, this._lView, token, convertToBitFlags(flags), notFoundValue);
- }
- }
- /** Creates a `NodeInjector` for the current node. */
- function createNodeInjector() {
- return new NodeInjector(getCurrentTNode(), getLView());
- }
- /**
- * @codeGenApi
- */
- function ɵɵgetInheritedFactory(type) {
- return noSideEffects(() => {
- const ownConstructor = type.prototype.constructor;
- const ownFactory = ownConstructor[NG_FACTORY_DEF] || getFactoryOf(ownConstructor);
- const objectPrototype = Object.prototype;
- let parent = Object.getPrototypeOf(type.prototype).constructor;
- // Go up the prototype until we hit `Object`.
- while (parent && parent !== objectPrototype) {
- const factory = parent[NG_FACTORY_DEF] || getFactoryOf(parent);
- // If we hit something that has a factory and the factory isn't the same as the type,
- // we've found the inherited factory. Note the check that the factory isn't the type's
- // own factory is redundant in most cases, but if the user has custom decorators on the
- // class, this lookup will start one level down in the prototype chain, causing us to
- // find the own factory first and potentially triggering an infinite loop downstream.
- if (factory && factory !== ownFactory) {
- return factory;
- }
- parent = Object.getPrototypeOf(parent);
- }
- // There is no factory defined. Either this was improper usage of inheritance
- // (no Angular decorator on the superclass) or there is no constructor at all
- // in the inheritance chain. Since the two cases cannot be distinguished, the
- // latter has to be assumed.
- return (t) => new t();
- });
- }
- function getFactoryOf(type) {
- if (isForwardRef(type)) {
- return () => {
- const factory = getFactoryOf(resolveForwardRef(type));
- return factory && factory();
- };
- }
- return getFactoryDef(type);
- }
- /**
- * Returns a value from the closest embedded or node injector.
- *
- * @param tNode The Node where the search for the injector should start
- * @param lView The `LView` that contains the `tNode`
- * @param token The token to look for
- * @param flags Injection flags
- * @param notFoundValue The value to return when the injection flags is `InjectFlags.Optional`
- * @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
- */
- function lookupTokenUsingEmbeddedInjector(tNode, lView, token, flags, notFoundValue) {
- let currentTNode = tNode;
- let currentLView = lView;
- // When an LView with an embedded view injector is inserted, it'll likely be interlaced with
- // nodes who may have injectors (e.g. node injector -> embedded view injector -> node injector).
- // Since the bloom filters for the node injectors have already been constructed and we don't
- // have a way of extracting the records from an injector, the only way to maintain the correct
- // hierarchy when resolving the value is to walk it node-by-node while attempting to resolve
- // the token at each level.
- while (currentTNode !== null && currentLView !== null &&
- (currentLView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */) &&
- !(currentLView[FLAGS] & 512 /* LViewFlags.IsRoot */)) {
- ngDevMode && assertTNodeForLView(currentTNode, currentLView);
- // Note that this lookup on the node injector is using the `Self` flag, because
- // we don't want the node injector to look at any parent injectors since we
- // may hit the embedded view injector first.
- const nodeInjectorValue = lookupTokenUsingNodeInjector(currentTNode, currentLView, token, flags | InjectFlags.Self, NOT_FOUND);
- if (nodeInjectorValue !== NOT_FOUND) {
- return nodeInjectorValue;
- }
- // Has an explicit type due to a TS bug: https://github.com/microsoft/TypeScript/issues/33191
- let parentTNode = currentTNode.parent;
- // `TNode.parent` includes the parent within the current view only. If it doesn't exist,
- // it means that we've hit the view boundary and we need to go up to the next view.
- if (!parentTNode) {
- // Before we go to the next LView, check if the token exists on the current embedded injector.
- const embeddedViewInjector = currentLView[EMBEDDED_VIEW_INJECTOR];
- if (embeddedViewInjector) {
- const embeddedViewInjectorValue = embeddedViewInjector.get(token, NOT_FOUND, flags);
- if (embeddedViewInjectorValue !== NOT_FOUND) {
- return embeddedViewInjectorValue;
- }
- }
- // Otherwise keep going up the tree.
- parentTNode = getTNodeFromLView(currentLView);
- currentLView = currentLView[DECLARATION_VIEW];
- }
- currentTNode = parentTNode;
- }
- return notFoundValue;
- }
- /** Gets the TNode associated with an LView inside of the declaration view. */
- function getTNodeFromLView(lView) {
- const tView = lView[TVIEW];
- const tViewType = tView.type;
- // The parent pointer differs based on `TView.type`.
- if (tViewType === 2 /* TViewType.Embedded */) {
- ngDevMode && assertDefined(tView.declTNode, 'Embedded TNodes should have declaration parents.');
- return tView.declTNode;
- }
- else if (tViewType === 1 /* TViewType.Component */) {
- // Components don't have `TView.declTNode` because each instance of component could be
- // inserted in different location, hence `TView.declTNode` is meaningless.
- return lView[T_HOST];
- }
- return null;
- }
- /**
- * Facade for the attribute injection from DI.
- *
- * @codeGenApi
- */
- function ɵɵinjectAttribute(attrNameToInject) {
- return injectAttributeImpl(getCurrentTNode(), attrNameToInject);
- }
- /**
- * Attribute decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const Attribute = makeParamDecorator('Attribute', (attributeName) => ({ attributeName, __NG_ELEMENT_ID__: () => ɵɵinjectAttribute(attributeName) }));
- let _reflect = null;
- function getReflect() {
- return (_reflect = _reflect || new ReflectionCapabilities());
- }
- function reflectDependencies(type) {
- return convertDependencies(getReflect().parameters(type));
- }
- function convertDependencies(deps) {
- return deps.map(dep => reflectDependency(dep));
- }
- function reflectDependency(dep) {
- const meta = {
- token: null,
- attribute: null,
- host: false,
- optional: false,
- self: false,
- skipSelf: false,
- };
- if (Array.isArray(dep) && dep.length > 0) {
- for (let j = 0; j < dep.length; j++) {
- const param = dep[j];
- if (param === undefined) {
- // param may be undefined if type of dep is not set by ngtsc
- continue;
- }
- const proto = Object.getPrototypeOf(param);
- if (param instanceof Optional || proto.ngMetadataName === 'Optional') {
- meta.optional = true;
- }
- else if (param instanceof SkipSelf || proto.ngMetadataName === 'SkipSelf') {
- meta.skipSelf = true;
- }
- else if (param instanceof Self || proto.ngMetadataName === 'Self') {
- meta.self = true;
- }
- else if (param instanceof Host || proto.ngMetadataName === 'Host') {
- meta.host = true;
- }
- else if (param instanceof Inject) {
- meta.token = param.token;
- }
- else if (param instanceof Attribute) {
- if (param.attributeName === undefined) {
- throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && `Attribute name must be defined.`);
- }
- meta.attribute = param.attributeName;
- }
- else {
- meta.token = param;
- }
- }
- }
- else if (dep === undefined || (Array.isArray(dep) && dep.length === 0)) {
- meta.token = null;
- }
- else {
- meta.token = dep;
- }
- return meta;
- }
- /**
- * Map of module-id to the corresponding NgModule.
- */
- const modules = new Map();
- /**
- * Whether to check for duplicate NgModule registrations.
- *
- * This can be disabled for testing.
- */
- let checkForDuplicateNgModules = true;
- function assertSameOrNotExisting(id, type, incoming) {
- if (type && type !== incoming && checkForDuplicateNgModules) {
- throw new Error(`Duplicate module registered for ${id} - ${stringify(type)} vs ${stringify(type.name)}`);
- }
- }
- /**
- * Adds the given NgModule type to Angular's NgModule registry.
- *
- * This is generated as a side-effect of NgModule compilation. Note that the `id` is passed in
- * explicitly and not read from the NgModule definition. This is for two reasons: it avoids a
- * megamorphic read, and in JIT there's a chicken-and-egg problem where the NgModule may not be
- * fully resolved when it's registered.
- *
- * @codeGenApi
- */
- function registerNgModuleType(ngModuleType, id) {
- const existing = modules.get(id) || null;
- assertSameOrNotExisting(id, existing, ngModuleType);
- modules.set(id, ngModuleType);
- }
- function clearModulesForTest() {
- modules.clear();
- }
- function getRegisteredNgModuleType(id) {
- return modules.get(id);
- }
- /**
- * Control whether the NgModule registration system enforces that each NgModule type registered has
- * a unique id.
- *
- * This is useful for testing as the NgModule registry cannot be properly reset between tests with
- * Angular's current API.
- */
- function setAllowDuplicateNgModuleIdsForTest(allowDuplicates) {
- checkForDuplicateNgModules = !allowDuplicates;
- }
- /**
- * Creates a token that can be used in a DI Provider.
- *
- * Use an `InjectionToken` whenever the type you are injecting is not reified (does not have a
- * runtime representation) such as when injecting an interface, callable type, array or
- * parameterized type.
- *
- * `InjectionToken` is parameterized on `T` which is the type of object which will be returned by
- * the `Injector`. This provides an additional level of type safety.
- *
- * <div class="alert is-helpful">
- *
- * **Important Note**: Ensure that you use the same instance of the `InjectionToken` in both the
- * provider and the injection call. Creating a new instance of `InjectionToken` in different places,
- * even with the same description, will be treated as different tokens by Angular's DI system,
- * leading to a `NullInjectorError`.
- *
- * </div>
- *
- * <code-example format="typescript" language="typescript" path="injection-token/src/main.ts"
- * region="InjectionToken"></code-example>
- *
- * When creating an `InjectionToken`, you can optionally specify a factory function which returns
- * (possibly by creating) a default value of the parameterized type `T`. This sets up the
- * `InjectionToken` using this factory as a provider as if it was defined explicitly in the
- * application's root injector. If the factory function, which takes zero arguments, needs to inject
- * dependencies, it can do so using the [`inject`](api/core/inject) function.
- * As you can see in the Tree-shakable InjectionToken example below.
- *
- * Additionally, if a `factory` is specified you can also specify the `providedIn` option, which
- * overrides the above behavior and marks the token as belonging to a particular `@NgModule` (note:
- * this option is now deprecated). As mentioned above, `'root'` is the default value for
- * `providedIn`.
- *
- * The `providedIn: NgModule` and `providedIn: 'any'` options are deprecated.
- *
- * @usageNotes
- * ### Basic Examples
- *
- * ### Plain InjectionToken
- *
- * {@example core/di/ts/injector_spec.ts region='InjectionToken'}
- *
- * ### Tree-shakable InjectionToken
- *
- * {@example core/di/ts/injector_spec.ts region='ShakableInjectionToken'}
- *
- * @publicApi
- */
- class InjectionToken {
- /**
- * @param _desc Description for the token,
- * used only for debugging purposes,
- * it should but does not need to be unique
- * @param options Options for the token's usage, as described above
- */
- constructor(_desc, options) {
- this._desc = _desc;
- /** @internal */
- this.ngMetadataName = 'InjectionToken';
- this.ɵprov = undefined;
- if (typeof options == 'number') {
- (typeof ngDevMode === 'undefined' || ngDevMode) &&
- assertLessThan(options, 0, 'Only negative numbers are supported here');
- // This is a special hack to assign __NG_ELEMENT_ID__ to this instance.
- // See `InjectorMarkers`
- this.__NG_ELEMENT_ID__ = options;
- }
- else if (options !== undefined) {
- this.ɵprov = ɵɵdefineInjectable({
- token: this,
- providedIn: options.providedIn || 'root',
- factory: options.factory,
- });
- }
- }
- /**
- * @internal
- */
- get multi() {
- return this;
- }
- toString() {
- return `InjectionToken ${this._desc}`;
- }
- }
- /**
- * Most of the use of `document` in Angular is from within the DI system so it is possible to simply
- * inject the `DOCUMENT` token and are done.
- *
- * Ivy is special because it does not rely upon the DI and must get hold of the document some other
- * way.
- *
- * The solution is to define `getDocument()` and `setDocument()` top-level functions for ivy.
- * Wherever ivy needs the global document, it calls `getDocument()` instead.
- *
- * When running ivy outside of a browser environment, it is necessary to call `setDocument()` to
- * tell ivy what the global `document` is.
- *
- * Angular does this for us in each of the standard platforms (`Browser` and `Server`)
- * by calling `setDocument()` when providing the `DOCUMENT` token.
- */
- let DOCUMENT = undefined;
- /**
- * Tell ivy what the `document` is for this platform.
- *
- * It is only necessary to call this if the current platform is not a browser.
- *
- * @param document The object representing the global `document` in this environment.
- */
- function setDocument(document) {
- DOCUMENT = document;
- }
- /**
- * Access the object that represents the `document` for this platform.
- *
- * Ivy calls this whenever it needs to access the `document` object.
- * For example to create the renderer or to do sanitization.
- */
- function getDocument() {
- if (DOCUMENT !== undefined) {
- return DOCUMENT;
- }
- else if (typeof document !== 'undefined') {
- return document;
- }
- throw new RuntimeError(210 /* RuntimeErrorCode.MISSING_DOCUMENT */, (typeof ngDevMode === 'undefined' || ngDevMode) &&
- `The document object is not available in this context. Make sure the DOCUMENT injection token is provided.`);
- // No "document" can be found. This should only happen if we are running ivy outside Angular and
- // the current platform is not a browser. Since this is not a supported scenario at the moment
- // this should not happen in Angular apps.
- // Once we support running ivy outside of Angular we will need to publish `setDocument()` as a
- // public API.
- }
- /**
- * A [DI token](guide/glossary#di-token "DI token definition") representing a string ID, used
- * primarily for prefixing application attributes and CSS styles when
- * {@link ViewEncapsulation#Emulated} is being used.
- *
- * The token is needed in cases when multiple applications are bootstrapped on a page
- * (for example, using `bootstrapApplication` calls). In this case, ensure that those applications
- * have different `APP_ID` value setup. For example:
- *
- * ```
- * bootstrapApplication(ComponentA, {
- * providers: [
- * { provide: APP_ID, useValue: 'app-a' },
- * // ... other providers ...
- * ]
- * });
- *
- * bootstrapApplication(ComponentB, {
- * providers: [
- * { provide: APP_ID, useValue: 'app-b' },
- * // ... other providers ...
- * ]
- * });
- * ```
- *
- * By default, when there is only one application bootstrapped, you don't need to provide the
- * `APP_ID` token (the `ng` will be used as an app ID).
- *
- * @publicApi
- */
- const APP_ID = new InjectionToken('AppId', {
- providedIn: 'root',
- factory: () => DEFAULT_APP_ID,
- });
- /** Default value of the `APP_ID` token. */
- const DEFAULT_APP_ID = 'ng';
- /**
- * A function that is executed when a platform is initialized.
- * @publicApi
- */
- const PLATFORM_INITIALIZER = new InjectionToken('Platform Initializer');
- /**
- * A token that indicates an opaque platform ID.
- * @publicApi
- */
- const PLATFORM_ID = new InjectionToken('Platform ID', {
- providedIn: 'platform',
- factory: () => 'unknown', // set a default platform name, when none set explicitly
- });
- /**
- * A [DI token](guide/glossary#di-token "DI token definition") that indicates the root directory of
- * the application
- * @publicApi
- * @deprecated
- */
- const PACKAGE_ROOT_URL = new InjectionToken('Application Packages Root URL');
- // We keep this token here, rather than the animations package, so that modules that only care
- // about which animations module is loaded (e.g. the CDK) can retrieve it without having to
- // include extra dependencies. See #44970 for more context.
- /**
- * A [DI token](guide/glossary#di-token "DI token definition") that indicates which animations
- * module has been loaded.
- * @publicApi
- */
- const ANIMATION_MODULE_TYPE = new InjectionToken('AnimationModuleType');
- // TODO(crisbeto): link to CSP guide here.
- /**
- * Token used to configure the [Content Security Policy](https://web.dev/strict-csp/) nonce that
- * Angular will apply when inserting inline styles. If not provided, Angular will look up its value
- * from the `ngCspNonce` attribute of the application root node.
- *
- * @publicApi
- */
- const CSP_NONCE = new InjectionToken('CSP nonce', {
- providedIn: 'root',
- factory: () => {
- // Ideally we wouldn't have to use `querySelector` here since we know that the nonce will be on
- // the root node, but because the token value is used in renderers, it has to be available
- // *very* early in the bootstrapping process. This should be a fairly shallow search, because
- // the app won't have been added to the DOM yet. Some approaches that were considered:
- // 1. Find the root node through `ApplicationRef.components[i].location` - normally this would
- // be enough for our purposes, but the token is injected very early so the `components` array
- // isn't populated yet.
- // 2. Find the root `LView` through the current `LView` - renderers are a prerequisite to
- // creating the `LView`. This means that no `LView` will have been entered when this factory is
- // invoked for the root component.
- // 3. Have the token factory return `() => string` which is invoked when a nonce is requested -
- // the slightly later execution does allow us to get an `LView` reference, but the fact that
- // it is a function means that it could be executed at *any* time (including immediately) which
- // may lead to weird bugs.
- // 4. Have the `ComponentFactory` read the attribute and provide it to the injector under the
- // hood - has the same problem as #1 and #2 in that the renderer is used to query for the root
- // node and the nonce value needs to be available when the renderer is created.
- return getDocument().body?.querySelector('[ngCspNonce]')?.getAttribute('ngCspNonce') || null;
- },
- });
- /**
- * Internal token to collect all SSR-related features enabled for this application.
- *
- * Note: the token is in `core` to let other packages register features (the `core`
- * package is imported in other packages).
- */
- const ENABLED_SSR_FEATURES = new InjectionToken((typeof ngDevMode === 'undefined' || ngDevMode) ? 'ENABLED_SSR_FEATURES' : '', {
- providedIn: 'root',
- factory: () => new Set(),
- });
- /**
- * A multi-provider token for initialization functions that will run upon construction of an
- * environment injector.
- *
- * @publicApi
- */
- const ENVIRONMENT_INITIALIZER = new InjectionToken('ENVIRONMENT_INITIALIZER');
- /**
- * An InjectionToken that gets the current `Injector` for `createInjector()`-style injectors.
- *
- * Requesting this token instead of `Injector` allows `StaticInjector` to be tree-shaken from a
- * project.
- *
- * @publicApi
- */
- const INJECTOR = new InjectionToken('INJECTOR',
- // Disable tslint because this is const enum which gets inlined not top level prop access.
- // tslint:disable-next-line: no-toplevel-property-access
- -1 /* InjectorMarkers.Injector */);
- const INJECTOR_DEF_TYPES = new InjectionToken('INJECTOR_DEF_TYPES');
- class NullInjector {
- get(token, notFoundValue = THROW_IF_NOT_FOUND) {
- if (notFoundValue === THROW_IF_NOT_FOUND) {
- const error = new Error(`NullInjectorError: No provider for ${stringify(token)}!`);
- error.name = 'NullInjectorError';
- throw error;
- }
- return notFoundValue;
- }
- }
- /**
- * Wrap an array of `Provider`s into `EnvironmentProviders`, preventing them from being accidentally
- * referenced in `@Component` in a component injector.
- */
- function makeEnvironmentProviders(providers) {
- return {
- ɵproviders: providers,
- };
- }
- /**
- * Collects providers from all NgModules and standalone components, including transitively imported
- * ones.
- *
- * Providers extracted via `importProvidersFrom` are only usable in an application injector or
- * another environment injector (such as a route injector). They should not be used in component
- * providers.
- *
- * More information about standalone components can be found in [this
- * guide](guide/standalone-components).
- *
- * @usageNotes
- * The results of the `importProvidersFrom` call can be used in the `bootstrapApplication` call:
- *
- * ```typescript
- * await bootstrapApplication(RootComponent, {
- * providers: [
- * importProvidersFrom(NgModuleOne, NgModuleTwo)
- * ]
- * });
- * ```
- *
- * You can also use the `importProvidersFrom` results in the `providers` field of a route, when a
- * standalone component is used:
- *
- * ```typescript
- * export const ROUTES: Route[] = [
- * {
- * path: 'foo',
- * providers: [
- * importProvidersFrom(NgModuleOne, NgModuleTwo)
- * ],
- * component: YourStandaloneComponent
- * }
- * ];
- * ```
- *
- * @returns Collected providers from the specified list of types.
- * @publicApi
- */
- function importProvidersFrom(...sources) {
- return {
- ɵproviders: internalImportProvidersFrom(true, sources),
- ɵfromNgModule: true,
- };
- }
- function internalImportProvidersFrom(checkForStandaloneCmp, ...sources) {
- const providersOut = [];
- const dedup = new Set(); // already seen types
- let injectorTypesWithProviders;
- const collectProviders = (provider) => {
- providersOut.push(provider);
- };
- deepForEach(sources, source => {
- if ((typeof ngDevMode === 'undefined' || ngDevMode) && checkForStandaloneCmp) {
- const cmpDef = getComponentDef$1(source);
- if (cmpDef?.standalone) {
- throw new RuntimeError(800 /* RuntimeErrorCode.IMPORT_PROVIDERS_FROM_STANDALONE */, `Importing providers supports NgModule or ModuleWithProviders but got a standalone component "${stringifyForError(source)}"`);
- }
- }
- // Narrow `source` to access the internal type analogue for `ModuleWithProviders`.
- const internalSource = source;
- if (walkProviderTree(internalSource, collectProviders, [], dedup)) {
- injectorTypesWithProviders ||= [];
- injectorTypesWithProviders.push(internalSource);
- }
- });
- // Collect all providers from `ModuleWithProviders` types.
- if (injectorTypesWithProviders !== undefined) {
- processInjectorTypesWithProviders(injectorTypesWithProviders, collectProviders);
- }
- return providersOut;
- }
- /**
- * Collects all providers from the list of `ModuleWithProviders` and appends them to the provided
- * array.
- */
- function processInjectorTypesWithProviders(typesWithProviders, visitor) {
- for (let i = 0; i < typesWithProviders.length; i++) {
- const { ngModule, providers } = typesWithProviders[i];
- deepForEachProvider(providers, provider => {
- ngDevMode && validateProvider(provider, providers || EMPTY_ARRAY, ngModule);
- visitor(provider, ngModule);
- });
- }
- }
- /**
- * The logic visits an `InjectorType`, an `InjectorTypeWithProviders`, or a standalone
- * `ComponentType`, and all of its transitive providers and collects providers.
- *
- * If an `InjectorTypeWithProviders` that declares providers besides the type is specified,
- * the function will return "true" to indicate that the providers of the type definition need
- * to be processed. This allows us to process providers of injector types after all imports of
- * an injector definition are processed. (following View Engine semantics: see FW-1349)
- */
- function walkProviderTree(container, visitor, parents, dedup) {
- container = resolveForwardRef(container);
- if (!container)
- return false;
- // The actual type which had the definition. Usually `container`, but may be an unwrapped type
- // from `InjectorTypeWithProviders`.
- let defType = null;
- let injDef = getInjectorDef(container);
- const cmpDef = !injDef && getComponentDef$1(container);
- if (!injDef && !cmpDef) {
- // `container` is not an injector type or a component type. It might be:
- // * An `InjectorTypeWithProviders` that wraps an injector type.
- // * A standalone directive or pipe that got pulled in from a standalone component's
- // dependencies.
- // Try to unwrap it as an `InjectorTypeWithProviders` first.
- const ngModule = container.ngModule;
- injDef = getInjectorDef(ngModule);
- if (injDef) {
- defType = ngModule;
- }
- else {
- // Not a component or injector type, so ignore it.
- return false;
- }
- }
- else if (cmpDef && !cmpDef.standalone) {
- return false;
- }
- else {
- defType = container;
- }
- // Check for circular dependencies.
- if (ngDevMode && parents.indexOf(defType) !== -1) {
- const defName = stringify(defType);
- const path = parents.map(stringify);
- throwCyclicDependencyError(defName, path);
- }
- // Check for multiple imports of the same module
- const isDuplicate = dedup.has(defType);
- if (cmpDef) {
- if (isDuplicate) {
- // This component definition has already been processed.
- return false;
- }
- dedup.add(defType);
- if (cmpDef.dependencies) {
- const deps = typeof cmpDef.dependencies === 'function' ? cmpDef.dependencies() : cmpDef.dependencies;
- for (const dep of deps) {
- walkProviderTree(dep, visitor, parents, dedup);
- }
- }
- }
- else if (injDef) {
- // First, include providers from any imports.
- if (injDef.imports != null && !isDuplicate) {
- // Before processing defType's imports, add it to the set of parents. This way, if it ends
- // up deeply importing itself, this can be detected.
- ngDevMode && parents.push(defType);
- // Add it to the set of dedups. This way we can detect multiple imports of the same module
- dedup.add(defType);
- let importTypesWithProviders;
- try {
- deepForEach(injDef.imports, imported => {
- if (walkProviderTree(imported, visitor, parents, dedup)) {
- importTypesWithProviders ||= [];
- // If the processed import is an injector type with providers, we store it in the
- // list of import types with providers, so that we can process those afterwards.
- importTypesWithProviders.push(imported);
- }
- });
- }
- finally {
- // Remove it from the parents set when finished.
- ngDevMode && parents.pop();
- }
- // Imports which are declared with providers (TypeWithProviders) need to be processed
- // after all imported modules are processed. This is similar to how View Engine
- // processes/merges module imports in the metadata resolver. See: FW-1349.
- if (importTypesWithProviders !== undefined) {
- processInjectorTypesWithProviders(importTypesWithProviders, visitor);
- }
- }
- if (!isDuplicate) {
- // Track the InjectorType and add a provider for it.
- // It's important that this is done after the def's imports.
- const factory = getFactoryDef(defType) || (() => new defType());
- // Append extra providers to make more info available for consumers (to retrieve an injector
- // type), as well as internally (to calculate an injection scope correctly and eagerly
- // instantiate a `defType` when an injector is created).
- // Provider to create `defType` using its factory.
- visitor({ provide: defType, useFactory: factory, deps: EMPTY_ARRAY }, defType);
- // Make this `defType` available to an internal logic that calculates injector scope.
- visitor({ provide: INJECTOR_DEF_TYPES, useValue: defType, multi: true }, defType);
- // Provider to eagerly instantiate `defType` via `INJECTOR_INITIALIZER`.
- visitor({ provide: ENVIRONMENT_INITIALIZER, useValue: () => ɵɵinject(defType), multi: true }, defType);
- }
- // Next, include providers listed on the definition itself.
- const defProviders = injDef.providers;
- if (defProviders != null && !isDuplicate) {
- const injectorType = container;
- deepForEachProvider(defProviders, provider => {
- ngDevMode && validateProvider(provider, defProviders, injectorType);
- visitor(provider, injectorType);
- });
- }
- }
- else {
- // Should not happen, but just in case.
- return false;
- }
- return (defType !== container &&
- container.providers !== undefined);
- }
- function validateProvider(provider, providers, containerType) {
- if (isTypeProvider(provider) || isValueProvider(provider) || isFactoryProvider(provider) ||
- isExistingProvider(provider)) {
- return;
- }
- // Here we expect the provider to be a `useClass` provider (by elimination).
- const classRef = resolveForwardRef(provider && (provider.useClass || provider.provide));
- if (!classRef) {
- throwInvalidProviderError(containerType, providers, provider);
- }
- }
- function deepForEachProvider(providers, fn) {
- for (let provider of providers) {
- if (isEnvironmentProviders(provider)) {
- provider = provider.ɵproviders;
- }
- if (Array.isArray(provider)) {
- deepForEachProvider(provider, fn);
- }
- else {
- fn(provider);
- }
- }
- }
- const USE_VALUE$1 = getClosureSafeProperty({ provide: String, useValue: getClosureSafeProperty });
- function isValueProvider(value) {
- return value !== null && typeof value == 'object' && USE_VALUE$1 in value;
- }
- function isExistingProvider(value) {
- return !!(value && value.useExisting);
- }
- function isFactoryProvider(value) {
- return !!(value && value.useFactory);
- }
- function isTypeProvider(value) {
- return typeof value === 'function';
- }
- function isClassProvider(value) {
- return !!value.useClass;
- }
- /**
- * An internal token whose presence in an injector indicates that the injector should treat itself
- * as a root scoped injector when processing requests for unknown tokens which may indicate
- * they are provided in the root scope.
- */
- const INJECTOR_SCOPE = new InjectionToken('Set Injector scope.');
- /**
- * Marker which indicates that a value has not yet been created from the factory function.
- */
- const NOT_YET = {};
- /**
- * Marker which indicates that the factory function for a token is in the process of being called.
- *
- * If the injector is asked to inject a token with its value set to CIRCULAR, that indicates
- * injection of a dependency has recursively attempted to inject the original token, and there is
- * a circular dependency among the providers.
- */
- const CIRCULAR = {};
- /**
- * A lazily initialized NullInjector.
- */
- let NULL_INJECTOR = undefined;
- function getNullInjector() {
- if (NULL_INJECTOR === undefined) {
- NULL_INJECTOR = new NullInjector();
- }
- return NULL_INJECTOR;
- }
- /**
- * An `Injector` that's part of the environment injector hierarchy, which exists outside of the
- * component tree.
- */
- class EnvironmentInjector {
- }
- class R3Injector extends EnvironmentInjector {
- /**
- * Flag indicating that this injector was previously destroyed.
- */
- get destroyed() {
- return this._destroyed;
- }
- constructor(providers, parent, source, scopes) {
- super();
- this.parent = parent;
- this.source = source;
- this.scopes = scopes;
- /**
- * Map of tokens to records which contain the instances of those tokens.
- * - `null` value implies that we don't have the record. Used by tree-shakable injectors
- * to prevent further searches.
- */
- this.records = new Map();
- /**
- * Set of values instantiated by this injector which contain `ngOnDestroy` lifecycle hooks.
- */
- this._ngOnDestroyHooks = new Set();
- this._onDestroyHooks = [];
- this._destroyed = false;
- // Start off by creating Records for every provider.
- forEachSingleProvider(providers, provider => this.processProvider(provider));
- // Make sure the INJECTOR token provides this injector.
- this.records.set(INJECTOR, makeRecord(undefined, this));
- // And `EnvironmentInjector` if the current injector is supposed to be env-scoped.
- if (scopes.has('environment')) {
- this.records.set(EnvironmentInjector, makeRecord(undefined, this));
- }
- // Detect whether this injector has the APP_ROOT_SCOPE token and thus should provide
- // any injectable scoped to APP_ROOT_SCOPE.
- const record = this.records.get(INJECTOR_SCOPE);
- if (record != null && typeof record.value === 'string') {
- this.scopes.add(record.value);
- }
- this.injectorDefTypes =
- new Set(this.get(INJECTOR_DEF_TYPES.multi, EMPTY_ARRAY, InjectFlags.Self));
- }
- /**
- * Destroy the injector and release references to every instance or provider associated with it.
- *
- * Also calls the `OnDestroy` lifecycle hooks of every instance that was created for which a
- * hook was found.
- */
- destroy() {
- this.assertNotDestroyed();
- // Set destroyed = true first, in case lifecycle hooks re-enter destroy().
- this._destroyed = true;
- try {
- // Call all the lifecycle hooks.
- for (const service of this._ngOnDestroyHooks) {
- service.ngOnDestroy();
- }
- const onDestroyHooks = this._onDestroyHooks;
- // Reset the _onDestroyHooks array before iterating over it to prevent hooks that unregister
- // themselves from mutating the array during iteration.
- this._onDestroyHooks = [];
- for (const hook of onDestroyHooks) {
- hook();
- }
- }
- finally {
- // Release all references.
- this.records.clear();
- this._ngOnDestroyHooks.clear();
- this.injectorDefTypes.clear();
- }
- }
- onDestroy(callback) {
- this.assertNotDestroyed();
- this._onDestroyHooks.push(callback);
- return () => this.removeOnDestroy(callback);
- }
- runInContext(fn) {
- this.assertNotDestroyed();
- const previousInjector = setCurrentInjector(this);
- const previousInjectImplementation = setInjectImplementation(undefined);
- let prevInjectContext;
- if (ngDevMode) {
- prevInjectContext = setInjectorProfilerContext({ injector: this, token: null });
- }
- try {
- return fn();
- }
- finally {
- setCurrentInjector(previousInjector);
- setInjectImplementation(previousInjectImplementation);
- ngDevMode && setInjectorProfilerContext(prevInjectContext);
- }
- }
- get(token, notFoundValue = THROW_IF_NOT_FOUND, flags = InjectFlags.Default) {
- this.assertNotDestroyed();
- if (token.hasOwnProperty(NG_ENV_ID)) {
- return token[NG_ENV_ID](this);
- }
- flags = convertToBitFlags(flags);
- // Set the injection context.
- let prevInjectContext;
- if (ngDevMode) {
- prevInjectContext = setInjectorProfilerContext({ injector: this, token: token });
- }
- const previousInjector = setCurrentInjector(this);
- const previousInjectImplementation = setInjectImplementation(undefined);
- try {
- // Check for the SkipSelf flag.
- if (!(flags & InjectFlags.SkipSelf)) {
- // SkipSelf isn't set, check if the record belongs to this injector.
- let record = this.records.get(token);
- if (record === undefined) {
- // No record, but maybe the token is scoped to this injector. Look for an injectable
- // def with a scope matching this injector.
- const def = couldBeInjectableType(token) && getInjectableDef(token);
- if (def && this.injectableDefInScope(def)) {
- // Found an injectable def and it's scoped to this injector. Pretend as if it was here
- // all along.
- record = makeRecord(injectableDefOrInjectorDefFactory(token), NOT_YET);
- }
- else {
- record = null;
- }
- this.records.set(token, record);
- }
- // If a record was found, get the instance for it and return it.
- if (record != null /* NOT null || undefined */) {
- return this.hydrate(token, record);
- }
- }
- // Select the next injector based on the Self flag - if self is set, the next injector is
- // the NullInjector, otherwise it's the parent.
- const nextInjector = !(flags & InjectFlags.Self) ? this.parent : getNullInjector();
- // Set the notFoundValue based on the Optional flag - if optional is set and notFoundValue
- // is undefined, the value is null, otherwise it's the notFoundValue.
- notFoundValue = (flags & InjectFlags.Optional) && notFoundValue === THROW_IF_NOT_FOUND ?
- null :
- notFoundValue;
- return nextInjector.get(token, notFoundValue);
- }
- catch (e) {
- if (e.name === 'NullInjectorError') {
- const path = e[NG_TEMP_TOKEN_PATH] = e[NG_TEMP_TOKEN_PATH] || [];
- path.unshift(stringify(token));
- if (previousInjector) {
- // We still have a parent injector, keep throwing
- throw e;
- }
- else {
- // Format & throw the final error message when we don't have any previous injector
- return catchInjectorError(e, token, 'R3InjectorError', this.source);
- }
- }
- else {
- throw e;
- }
- }
- finally {
- // Lastly, restore the previous injection context.
- setInjectImplementation(previousInjectImplementation);
- setCurrentInjector(previousInjector);
- ngDevMode && setInjectorProfilerContext(prevInjectContext);
- }
- }
- /** @internal */
- resolveInjectorInitializers() {
- const previousInjector = setCurrentInjector(this);
- const previousInjectImplementation = setInjectImplementation(undefined);
- let prevInjectContext;
- if (ngDevMode) {
- prevInjectContext = setInjectorProfilerContext({ injector: this, token: null });
- }
- try {
- const initializers = this.get(ENVIRONMENT_INITIALIZER.multi, EMPTY_ARRAY, InjectFlags.Self);
- if (ngDevMode && !Array.isArray(initializers)) {
- throw new RuntimeError(-209 /* RuntimeErrorCode.INVALID_MULTI_PROVIDER */, 'Unexpected type of the `ENVIRONMENT_INITIALIZER` token value ' +
- `(expected an array, but got ${typeof initializers}). ` +
- 'Please check that the `ENVIRONMENT_INITIALIZER` token is configured as a ' +
- '`multi: true` provider.');
- }
- for (const initializer of initializers) {
- initializer();
- }
- }
- finally {
- setCurrentInjector(previousInjector);
- setInjectImplementation(previousInjectImplementation);
- ngDevMode && setInjectorProfilerContext(prevInjectContext);
- }
- }
- toString() {
- const tokens = [];
- const records = this.records;
- for (const token of records.keys()) {
- tokens.push(stringify(token));
- }
- return `R3Injector[${tokens.join(', ')}]`;
- }
- assertNotDestroyed() {
- if (this._destroyed) {
- throw new RuntimeError(205 /* RuntimeErrorCode.INJECTOR_ALREADY_DESTROYED */, ngDevMode && 'Injector has already been destroyed.');
- }
- }
- /**
- * Process a `SingleProvider` and add it.
- */
- processProvider(provider) {
- // Determine the token from the provider. Either it's its own token, or has a {provide: ...}
- // property.
- provider = resolveForwardRef(provider);
- let token = isTypeProvider(provider) ? provider : resolveForwardRef(provider && provider.provide);
- // Construct a `Record` for the provider.
- const record = providerToRecord(provider);
- if (ngDevMode) {
- runInInjectorProfilerContext(this, token, () => {
- // Emit InjectorProfilerEventType.Create if provider is a value provider because
- // these are the only providers that do not go through the value hydration logic
- // where this event would normally be emitted from.
- if (isValueProvider(provider)) {
- emitInstanceCreatedByInjectorEvent(provider.useValue);
- }
- emitProviderConfiguredEvent(provider);
- });
- }
- if (!isTypeProvider(provider) && provider.multi === true) {
- // If the provider indicates that it's a multi-provider, process it specially.
- // First check whether it's been defined already.
- let multiRecord = this.records.get(token);
- if (multiRecord) {
- // It has. Throw a nice error if
- if (ngDevMode && multiRecord.multi === undefined) {
- throwMixedMultiProviderError();
- }
- }
- else {
- multiRecord = makeRecord(undefined, NOT_YET, true);
- multiRecord.factory = () => injectArgs(multiRecord.multi);
- this.records.set(token, multiRecord);
- }
- token = provider;
- multiRecord.multi.push(provider);
- }
- else {
- const existing = this.records.get(token);
- if (ngDevMode && existing && existing.multi !== undefined) {
- throwMixedMultiProviderError();
- }
- }
- this.records.set(token, record);
- }
- hydrate(token, record) {
- if (ngDevMode && record.value === CIRCULAR) {
- throwCyclicDependencyError(stringify(token));
- }
- else if (record.value === NOT_YET) {
- record.value = CIRCULAR;
- if (ngDevMode) {
- runInInjectorProfilerContext(this, token, () => {
- record.value = record.factory();
- emitInstanceCreatedByInjectorEvent(record.value);
- });
- }
- else {
- record.value = record.factory();
- }
- }
- if (typeof record.value === 'object' && record.value && hasOnDestroy(record.value)) {
- this._ngOnDestroyHooks.add(record.value);
- }
- return record.value;
- }
- injectableDefInScope(def) {
- if (!def.providedIn) {
- return false;
- }
- const providedIn = resolveForwardRef(def.providedIn);
- if (typeof providedIn === 'string') {
- return providedIn === 'any' || (this.scopes.has(providedIn));
- }
- else {
- return this.injectorDefTypes.has(providedIn);
- }
- }
- removeOnDestroy(callback) {
- const destroyCBIdx = this._onDestroyHooks.indexOf(callback);
- if (destroyCBIdx !== -1) {
- this._onDestroyHooks.splice(destroyCBIdx, 1);
- }
- }
- }
- function injectableDefOrInjectorDefFactory(token) {
- // Most tokens will have an injectable def directly on them, which specifies a factory directly.
- const injectableDef = getInjectableDef(token);
- const factory = injectableDef !== null ? injectableDef.factory : getFactoryDef(token);
- if (factory !== null) {
- return factory;
- }
- // InjectionTokens should have an injectable def (ɵprov) and thus should be handled above.
- // If it's missing that, it's an error.
- if (token instanceof InjectionToken) {
- throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && `Token ${stringify(token)} is missing a ɵprov definition.`);
- }
- // Undecorated types can sometimes be created if they have no constructor arguments.
- if (token instanceof Function) {
- return getUndecoratedInjectableFactory(token);
- }
- // There was no way to resolve a factory for this token.
- throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && 'unreachable');
- }
- function getUndecoratedInjectableFactory(token) {
- // If the token has parameters then it has dependencies that we cannot resolve implicitly.
- const paramLength = token.length;
- if (paramLength > 0) {
- const args = newArray(paramLength, '?');
- throw new RuntimeError(204 /* RuntimeErrorCode.INVALID_INJECTION_TOKEN */, ngDevMode && `Can't resolve all parameters for ${stringify(token)}: (${args.join(', ')}).`);
- }
- // The constructor function appears to have no parameters.
- // This might be because it inherits from a super-class. In which case, use an injectable
- // def from an ancestor if there is one.
- // Otherwise this really is a simple class with no dependencies, so return a factory that
- // just instantiates the zero-arg constructor.
- const inheritedInjectableDef = getInheritedInjectableDef(token);
- if (inheritedInjectableDef !== null) {
- return () => inheritedInjectableDef.factory(token);
- }
- else {
- return () => new token();
- }
- }
- function providerToRecord(provider) {
- if (isValueProvider(provider)) {
- return makeRecord(undefined, provider.useValue);
- }
- else {
- const factory = providerToFactory(provider);
- return makeRecord(factory, NOT_YET);
- }
- }
- /**
- * Converts a `SingleProvider` into a factory function.
- *
- * @param provider provider to convert to factory
- */
- function providerToFactory(provider, ngModuleType, providers) {
- let factory = undefined;
- if (ngDevMode && isEnvironmentProviders(provider)) {
- throwInvalidProviderError(undefined, providers, provider);
- }
- if (isTypeProvider(provider)) {
- const unwrappedProvider = resolveForwardRef(provider);
- return getFactoryDef(unwrappedProvider) || injectableDefOrInjectorDefFactory(unwrappedProvider);
- }
- else {
- if (isValueProvider(provider)) {
- factory = () => resolveForwardRef(provider.useValue);
- }
- else if (isFactoryProvider(provider)) {
- factory = () => provider.useFactory(...injectArgs(provider.deps || []));
- }
- else if (isExistingProvider(provider)) {
- factory = () => ɵɵinject(resolveForwardRef(provider.useExisting));
- }
- else {
- const classRef = resolveForwardRef(provider &&
- (provider.useClass || provider.provide));
- if (ngDevMode && !classRef) {
- throwInvalidProviderError(ngModuleType, providers, provider);
- }
- if (hasDeps(provider)) {
- factory = () => new (classRef)(...injectArgs(provider.deps));
- }
- else {
- return getFactoryDef(classRef) || injectableDefOrInjectorDefFactory(classRef);
- }
- }
- }
- return factory;
- }
- function makeRecord(factory, value, multi = false) {
- return {
- factory: factory,
- value: value,
- multi: multi ? [] : undefined,
- };
- }
- function hasDeps(value) {
- return !!value.deps;
- }
- function hasOnDestroy(value) {
- return value !== null && typeof value === 'object' &&
- typeof value.ngOnDestroy === 'function';
- }
- function couldBeInjectableType(value) {
- return (typeof value === 'function') ||
- (typeof value === 'object' && value instanceof InjectionToken);
- }
- function forEachSingleProvider(providers, fn) {
- for (const provider of providers) {
- if (Array.isArray(provider)) {
- forEachSingleProvider(provider, fn);
- }
- else if (provider && isEnvironmentProviders(provider)) {
- forEachSingleProvider(provider.ɵproviders, fn);
- }
- else {
- fn(provider);
- }
- }
- }
- /**
- * Runs the given function in the [context](guide/dependency-injection-context) of the given
- * `Injector`.
- *
- * Within the function's stack frame, [`inject`](api/core/inject) can be used to inject dependencies
- * from the given `Injector`. Note that `inject` is only usable synchronously, and cannot be used in
- * any asynchronous callbacks or after any `await` points.
- *
- * @param injector the injector which will satisfy calls to [`inject`](api/core/inject) while `fn`
- * is executing
- * @param fn the closure to be run in the context of `injector`
- * @returns the return value of the function, if any
- * @publicApi
- */
- function runInInjectionContext(injector, fn) {
- if (injector instanceof R3Injector) {
- injector.assertNotDestroyed();
- }
- let prevInjectorProfilerContext;
- if (ngDevMode) {
- prevInjectorProfilerContext = setInjectorProfilerContext({ injector, token: null });
- }
- const prevInjector = setCurrentInjector(injector);
- const previousInjectImplementation = setInjectImplementation(undefined);
- try {
- return fn();
- }
- finally {
- setCurrentInjector(prevInjector);
- ngDevMode && setInjectorProfilerContext(prevInjectorProfilerContext);
- setInjectImplementation(previousInjectImplementation);
- }
- }
- /**
- * Asserts that the current stack frame is within an [injection
- * context](guide/dependency-injection-context) and has access to `inject`.
- *
- * @param debugFn a reference to the function making the assertion (used for the error message).
- *
- * @publicApi
- */
- function assertInInjectionContext(debugFn) {
- // Taking a `Function` instead of a string name here prevents the unminified name of the function
- // from being retained in the bundle regardless of minification.
- if (!getInjectImplementation() && !getCurrentInjector()) {
- throw new RuntimeError(-203 /* RuntimeErrorCode.MISSING_INJECTION_CONTEXT */, ngDevMode &&
- (debugFn.name +
- '() can only be used within an injection context such as a constructor, a factory function, a field initializer, or a function used with `runInInjectionContext`'));
- }
- }
- /**
- * A mapping of the @angular/core API surface used in generated expressions to the actual symbols.
- *
- * This should be kept up to date with the public exports of @angular/core.
- */
- const angularCoreDiEnv = {
- 'ɵɵdefineInjectable': ɵɵdefineInjectable,
- 'ɵɵdefineInjector': ɵɵdefineInjector,
- 'ɵɵinject': ɵɵinject,
- 'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep,
- 'resolveForwardRef': resolveForwardRef,
- };
- /**
- * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting
- * injectable def (`ɵprov`) onto the injectable type.
- */
- function compileInjectable(type, meta) {
- let ngInjectableDef = null;
- let ngFactoryDef = null;
- // if NG_PROV_DEF is already defined on this class then don't overwrite it
- if (!type.hasOwnProperty(NG_PROV_DEF)) {
- Object.defineProperty(type, NG_PROV_DEF, {
- get: () => {
- if (ngInjectableDef === null) {
- const compiler = getCompilerFacade({ usage: 0 /* JitCompilerUsage.Decorator */, kind: 'injectable', type });
- ngInjectableDef = compiler.compileInjectable(angularCoreDiEnv, `ng:///${type.name}/ɵprov.js`, getInjectableMetadata(type, meta));
- }
- return ngInjectableDef;
- },
- });
- }
- // if NG_FACTORY_DEF is already defined on this class then don't overwrite it
- if (!type.hasOwnProperty(NG_FACTORY_DEF)) {
- Object.defineProperty(type, NG_FACTORY_DEF, {
- get: () => {
- if (ngFactoryDef === null) {
- const compiler = getCompilerFacade({ usage: 0 /* JitCompilerUsage.Decorator */, kind: 'injectable', type });
- ngFactoryDef = compiler.compileFactory(angularCoreDiEnv, `ng:///${type.name}/ɵfac.js`, {
- name: type.name,
- type,
- typeArgumentCount: 0,
- deps: reflectDependencies(type),
- target: compiler.FactoryTarget.Injectable
- });
- }
- return ngFactoryDef;
- },
- // Leave this configurable so that the factories from directives or pipes can take precedence.
- configurable: true
- });
- }
- }
- const USE_VALUE = getClosureSafeProperty({ provide: String, useValue: getClosureSafeProperty });
- function isUseClassProvider(meta) {
- return meta.useClass !== undefined;
- }
- function isUseValueProvider(meta) {
- return USE_VALUE in meta;
- }
- function isUseFactoryProvider(meta) {
- return meta.useFactory !== undefined;
- }
- function isUseExistingProvider(meta) {
- return meta.useExisting !== undefined;
- }
- function getInjectableMetadata(type, srcMeta) {
- // Allow the compilation of a class with a `@Injectable()` decorator without parameters
- const meta = srcMeta || { providedIn: null };
- const compilerMeta = {
- name: type.name,
- type: type,
- typeArgumentCount: 0,
- providedIn: meta.providedIn,
- };
- if ((isUseClassProvider(meta) || isUseFactoryProvider(meta)) && meta.deps !== undefined) {
- compilerMeta.deps = convertDependencies(meta.deps);
- }
- // Check to see if the user explicitly provided a `useXxxx` property.
- if (isUseClassProvider(meta)) {
- compilerMeta.useClass = meta.useClass;
- }
- else if (isUseValueProvider(meta)) {
- compilerMeta.useValue = meta.useValue;
- }
- else if (isUseFactoryProvider(meta)) {
- compilerMeta.useFactory = meta.useFactory;
- }
- else if (isUseExistingProvider(meta)) {
- compilerMeta.useExisting = meta.useExisting;
- }
- return compilerMeta;
- }
- /**
- * Injectable decorator and metadata.
- *
- * @Annotation
- * @publicApi
- */
- const Injectable = makeDecorator('Injectable', undefined, undefined, undefined, (type, meta) => compileInjectable(type, meta));
- /**
- * Create a new `Injector` which is configured using a `defType` of `InjectorType<any>`s.
- */
- function createInjector(defType, parent = null, additionalProviders = null, name) {
- const injector = createInjectorWithoutInjectorInstances(defType, parent, additionalProviders, name);
- injector.resolveInjectorInitializers();
- return injector;
- }
- /**
- * Creates a new injector without eagerly resolving its injector types. Can be used in places
- * where resolving the injector types immediately can lead to an infinite loop. The injector types
- * should be resolved at a later point by calling `_resolveInjectorDefTypes`.
- */
- function createInjectorWithoutInjectorInstances(defType, parent = null, additionalProviders = null, name, scopes = new Set()) {
- const providers = [
- additionalProviders || EMPTY_ARRAY,
- importProvidersFrom(defType),
- ];
- name = name || (typeof defType === 'object' ? undefined : stringify(defType));
- return new R3Injector(providers, parent || getNullInjector(), name || null, scopes);
- }
- /**
- * Concrete injectors implement this interface. Injectors are configured
- * with [providers](guide/glossary#provider) that associate
- * dependencies of various types with [injection tokens](guide/glossary#di-token).
- *
- * @see ["DI Providers"](guide/dependency-injection-providers).
- * @see {@link StaticProvider}
- *
- * @usageNotes
- *
- * The following example creates a service injector instance.
- *
- * {@example core/di/ts/provider_spec.ts region='ConstructorProvider'}
- *
- * ### Usage example
- *
- * {@example core/di/ts/injector_spec.ts region='Injector'}
- *
- * `Injector` returns itself when given `Injector` as a token:
- *
- * {@example core/di/ts/injector_spec.ts region='injectInjector'}
- *
- * @publicApi
- */
- class Injector {
- static { this.THROW_IF_NOT_FOUND = THROW_IF_NOT_FOUND; }
- static { this.NULL = ( /* @__PURE__ */new NullInjector()); }
- static create(options, parent) {
- if (Array.isArray(options)) {
- return createInjector({ name: '' }, parent, options, '');
- }
- else {
- const name = options.name ?? '';
- return createInjector({ name }, options.parent, options.providers, name);
- }
- }
- /** @nocollapse */
- static { this.ɵprov = ɵɵdefineInjectable({
- token: Injector,
- providedIn: 'any',
- factory: () => ɵɵinject(INJECTOR),
- }); }
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ELEMENT_ID__ = -1 /* InjectorMarkers.Injector */; }
- }
- /**
- * @module
- * @description
- * The `di` module provides dependency injection container services.
- */
- /**
- * This file should not be necessary because node resolution should just default to `./di/index`!
- *
- * However it does not seem to work and it breaks:
- * - //packages/animations/browser/test:test_web_chromium-local
- * - //packages/compiler-cli/test:extract_i18n
- * - //packages/compiler-cli/test:ngc
- * - //packages/compiler-cli/test:perform_watch
- * - //packages/compiler-cli/test/diagnostics:check_types
- * - //packages/compiler-cli/test/transformers:test
- * - //packages/compiler/test:test
- * - //tools/public_api_guard:core_api
- *
- * Remove this file once the above is solved or wait until `ngc` is deleted and then it should be
- * safe to delete this file.
- */
- /**
- *
- * @codeGenApi
- */
- function ɵɵresolveWindow(element) {
- return element.ownerDocument.defaultView;
- }
- /**
- *
- * @codeGenApi
- */
- function ɵɵresolveDocument(element) {
- return element.ownerDocument;
- }
- /**
- *
- * @codeGenApi
- */
- function ɵɵresolveBody(element) {
- return element.ownerDocument.body;
- }
- /**
- * The special delimiter we use to separate property names, prefixes, and suffixes
- * in property binding metadata. See storeBindingMetadata().
- *
- * We intentionally use the Unicode "REPLACEMENT CHARACTER" (U+FFFD) as a delimiter
- * because it is a very uncommon character that is unlikely to be part of a user's
- * property names or interpolation strings. If it is in fact used in a property
- * binding, DebugElement.properties will not return the correct value for that
- * binding. However, there should be no runtime effect for real applications.
- *
- * This character is typically rendered as a question mark inside of a diamond.
- * See https://en.wikipedia.org/wiki/Specials_(Unicode_block)
- *
- */
- const INTERPOLATION_DELIMITER = `�`;
- /**
- * Unwrap a value which might be behind a closure (for forward declaration reasons).
- */
- function maybeUnwrapFn$1(value) {
- if (value instanceof Function) {
- return value();
- }
- else {
- return value;
- }
- }
- /**
- * Detects whether the code is invoked in a browser.
- * Later on, this check should be replaced with a tree-shakable
- * flag (e.g. `!isServer`).
- */
- function isPlatformBrowser(injector) {
- return (injector ?? inject$1(Injector)).get(PLATFORM_ID) === 'browser';
- }
- /**
- * Defines a schema that allows an NgModule to contain the following:
- * - Non-Angular elements named with dash case (`-`).
- * - Element properties named with dash case (`-`).
- * Dash case is the naming convention for custom elements.
- *
- * @publicApi
- */
- const CUSTOM_ELEMENTS_SCHEMA = {
- name: 'custom-elements'
- };
- /**
- * Defines a schema that allows any property on any element.
- *
- * This schema allows you to ignore the errors related to any unknown elements or properties in a
- * template. The usage of this schema is generally discouraged because it prevents useful validation
- * and may hide real errors in your template. Consider using the `CUSTOM_ELEMENTS_SCHEMA` instead.
- *
- * @publicApi
- */
- const NO_ERRORS_SCHEMA = {
- name: 'no-errors-schema'
- };
- let shouldThrowErrorOnUnknownElement = false;
- /**
- * Sets a strict mode for JIT-compiled components to throw an error on unknown elements,
- * instead of just logging the error.
- * (for AOT-compiled ones this check happens at build time).
- */
- function ɵsetUnknownElementStrictMode(shouldThrow) {
- shouldThrowErrorOnUnknownElement = shouldThrow;
- }
- /**
- * Gets the current value of the strict mode.
- */
- function ɵgetUnknownElementStrictMode() {
- return shouldThrowErrorOnUnknownElement;
- }
- let shouldThrowErrorOnUnknownProperty = false;
- /**
- * Sets a strict mode for JIT-compiled components to throw an error on unknown properties,
- * instead of just logging the error.
- * (for AOT-compiled ones this check happens at build time).
- */
- function ɵsetUnknownPropertyStrictMode(shouldThrow) {
- shouldThrowErrorOnUnknownProperty = shouldThrow;
- }
- /**
- * Gets the current value of the strict mode.
- */
- function ɵgetUnknownPropertyStrictMode() {
- return shouldThrowErrorOnUnknownProperty;
- }
- /**
- * Validates that the element is known at runtime and produces
- * an error if it's not the case.
- * This check is relevant for JIT-compiled components (for AOT-compiled
- * ones this check happens at build time).
- *
- * The element is considered known if either:
- * - it's a known HTML element
- * - it's a known custom element
- * - the element matches any directive
- * - the element is allowed by one of the schemas
- *
- * @param element Element to validate
- * @param lView An `LView` that represents a current component that is being rendered
- * @param tagName Name of the tag to check
- * @param schemas Array of schemas
- * @param hasDirectives Boolean indicating that the element matches any directive
- */
- function validateElementIsKnown(element, lView, tagName, schemas, hasDirectives) {
- // If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
- // mode where this check happens at compile time. In JIT mode, `schemas` is always present and
- // defined as an array (as an empty array in case `schemas` field is not defined) and we should
- // execute the check below.
- if (schemas === null)
- return;
- // If the element matches any directive, it's considered as valid.
- if (!hasDirectives && tagName !== null) {
- // The element is unknown if it's an instance of HTMLUnknownElement, or it isn't registered
- // as a custom element. Note that unknown elements with a dash in their name won't be instances
- // of HTMLUnknownElement in browsers that support web components.
- const isUnknown =
- // Note that we can't check for `typeof HTMLUnknownElement === 'function'` because
- // Domino doesn't expose HTMLUnknownElement globally.
- (typeof HTMLUnknownElement !== 'undefined' && HTMLUnknownElement &&
- element instanceof HTMLUnknownElement) ||
- (typeof customElements !== 'undefined' && tagName.indexOf('-') > -1 &&
- !customElements.get(tagName));
- if (isUnknown && !matchingSchemas(schemas, tagName)) {
- const isHostStandalone = isHostComponentStandalone(lView);
- const templateLocation = getTemplateLocationDetails(lView);
- const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`;
- let message = `'${tagName}' is not a known element${templateLocation}:\n`;
- message += `1. If '${tagName}' is an Angular component, then verify that it is ${isHostStandalone ? 'included in the \'@Component.imports\' of this component' :
- 'a part of an @NgModule where this component is declared'}.\n`;
- if (tagName && tagName.indexOf('-') > -1) {
- message +=
- `2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' to the ${schemas} of this component to suppress this message.`;
- }
- else {
- message +=
- `2. To allow any element add 'NO_ERRORS_SCHEMA' to the ${schemas} of this component.`;
- }
- if (shouldThrowErrorOnUnknownElement) {
- throw new RuntimeError(304 /* RuntimeErrorCode.UNKNOWN_ELEMENT */, message);
- }
- else {
- console.error(formatRuntimeError(304 /* RuntimeErrorCode.UNKNOWN_ELEMENT */, message));
- }
- }
- }
- }
- /**
- * Validates that the property of the element is known at runtime and returns
- * false if it's not the case.
- * This check is relevant for JIT-compiled components (for AOT-compiled
- * ones this check happens at build time).
- *
- * The property is considered known if either:
- * - it's a known property of the element
- * - the element is allowed by one of the schemas
- * - the property is used for animations
- *
- * @param element Element to validate
- * @param propName Name of the property to check
- * @param tagName Name of the tag hosting the property
- * @param schemas Array of schemas
- */
- function isPropertyValid(element, propName, tagName, schemas) {
- // If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
- // mode where this check happens at compile time. In JIT mode, `schemas` is always present and
- // defined as an array (as an empty array in case `schemas` field is not defined) and we should
- // execute the check below.
- if (schemas === null)
- return true;
- // The property is considered valid if the element matches the schema, it exists on the element,
- // or it is synthetic.
- if (matchingSchemas(schemas, tagName) || propName in element || isAnimationProp(propName)) {
- return true;
- }
- // Note: `typeof Node` returns 'function' in most browsers, but is undefined with domino.
- return typeof Node === 'undefined' || Node === null || !(element instanceof Node);
- }
- /**
- * Logs or throws an error that a property is not supported on an element.
- *
- * @param propName Name of the invalid property
- * @param tagName Name of the tag hosting the property
- * @param nodeType Type of the node hosting the property
- * @param lView An `LView` that represents a current component
- */
- function handleUnknownPropertyError(propName, tagName, nodeType, lView) {
- // Special-case a situation when a structural directive is applied to
- // an `<ng-template>` element, for example: `<ng-template *ngIf="true">`.
- // In this case the compiler generates the `ɵɵtemplate` instruction with
- // the `null` as the tagName. The directive matching logic at runtime relies
- // on this effect (see `isInlineTemplate`), thus using the 'ng-template' as
- // a default value of the `tNode.value` is not feasible at this moment.
- if (!tagName && nodeType === 4 /* TNodeType.Container */) {
- tagName = 'ng-template';
- }
- const isHostStandalone = isHostComponentStandalone(lView);
- const templateLocation = getTemplateLocationDetails(lView);
- let message = `Can't bind to '${propName}' since it isn't a known property of '${tagName}'${templateLocation}.`;
- const schemas = `'${isHostStandalone ? '@Component' : '@NgModule'}.schemas'`;
- const importLocation = isHostStandalone ?
- 'included in the \'@Component.imports\' of this component' :
- 'a part of an @NgModule where this component is declared';
- if (KNOWN_CONTROL_FLOW_DIRECTIVES.has(propName)) {
- // Most likely this is a control flow directive (such as `*ngIf`) used in
- // a template, but the directive or the `CommonModule` is not imported.
- const correspondingImport = KNOWN_CONTROL_FLOW_DIRECTIVES.get(propName);
- message += `\nIf the '${propName}' is an Angular control flow directive, ` +
- `please make sure that either the '${correspondingImport}' directive or the 'CommonModule' is ${importLocation}.`;
- }
- else {
- // May be an Angular component, which is not imported/declared?
- message += `\n1. If '${tagName}' is an Angular component and it has the ` +
- `'${propName}' input, then verify that it is ${importLocation}.`;
- // May be a Web Component?
- if (tagName && tagName.indexOf('-') > -1) {
- message += `\n2. If '${tagName}' is a Web Component then add 'CUSTOM_ELEMENTS_SCHEMA' ` +
- `to the ${schemas} of this component to suppress this message.`;
- message += `\n3. To allow any property add 'NO_ERRORS_SCHEMA' to ` +
- `the ${schemas} of this component.`;
- }
- else {
- // If it's expected, the error can be suppressed by the `NO_ERRORS_SCHEMA` schema.
- message += `\n2. To allow any property add 'NO_ERRORS_SCHEMA' to ` +
- `the ${schemas} of this component.`;
- }
- }
- reportUnknownPropertyError(message);
- }
- function reportUnknownPropertyError(message) {
- if (shouldThrowErrorOnUnknownProperty) {
- throw new RuntimeError(303 /* RuntimeErrorCode.UNKNOWN_BINDING */, message);
- }
- else {
- console.error(formatRuntimeError(303 /* RuntimeErrorCode.UNKNOWN_BINDING */, message));
- }
- }
- /**
- * WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`)
- * and must **not** be used in production bundles. The function makes megamorphic reads, which might
- * be too slow for production mode and also it relies on the constructor function being available.
- *
- * Gets a reference to the host component def (where a current component is declared).
- *
- * @param lView An `LView` that represents a current component that is being rendered.
- */
- function getDeclarationComponentDef(lView) {
- !ngDevMode && throwError('Must never be called in production mode');
- const declarationLView = lView[DECLARATION_COMPONENT_VIEW];
- const context = declarationLView[CONTEXT];
- // Unable to obtain a context.
- if (!context)
- return null;
- return context.constructor ? getComponentDef$1(context.constructor) : null;
- }
- /**
- * WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`)
- * and must **not** be used in production bundles. The function makes megamorphic reads, which might
- * be too slow for production mode.
- *
- * Checks if the current component is declared inside of a standalone component template.
- *
- * @param lView An `LView` that represents a current component that is being rendered.
- */
- function isHostComponentStandalone(lView) {
- !ngDevMode && throwError('Must never be called in production mode');
- const componentDef = getDeclarationComponentDef(lView);
- // Treat host component as non-standalone if we can't obtain the def.
- return !!componentDef?.standalone;
- }
- /**
- * WARNING: this is a **dev-mode only** function (thus should always be guarded by the `ngDevMode`)
- * and must **not** be used in production bundles. The function makes megamorphic reads, which might
- * be too slow for production mode.
- *
- * Constructs a string describing the location of the host component template. The function is used
- * in dev mode to produce error messages.
- *
- * @param lView An `LView` that represents a current component that is being rendered.
- */
- function getTemplateLocationDetails(lView) {
- !ngDevMode && throwError('Must never be called in production mode');
- const hostComponentDef = getDeclarationComponentDef(lView);
- const componentClassName = hostComponentDef?.type?.name;
- return componentClassName ? ` (used in the '${componentClassName}' component template)` : '';
- }
- /**
- * The set of known control flow directives and their corresponding imports.
- * We use this set to produce a more precises error message with a note
- * that the `CommonModule` should also be included.
- */
- const KNOWN_CONTROL_FLOW_DIRECTIVES = new Map([
- ['ngIf', 'NgIf'], ['ngFor', 'NgFor'], ['ngSwitchCase', 'NgSwitchCase'],
- ['ngSwitchDefault', 'NgSwitchDefault']
- ]);
- /**
- * Returns true if the tag name is allowed by specified schemas.
- * @param schemas Array of schemas
- * @param tagName Name of the tag
- */
- function matchingSchemas(schemas, tagName) {
- if (schemas !== null) {
- for (let i = 0; i < schemas.length; i++) {
- const schema = schemas[i];
- if (schema === NO_ERRORS_SCHEMA ||
- schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {
- return true;
- }
- }
- }
- return false;
- }
- /**
- * The name of an attribute that can be added to the hydration boundary node
- * (component host node) to disable hydration for the content within that boundary.
- */
- const SKIP_HYDRATION_ATTR_NAME = 'ngSkipHydration';
- /**
- * Helper function to check if a given TNode has the 'ngSkipHydration' attribute.
- */
- function hasSkipHydrationAttrOnTNode(tNode) {
- const SKIP_HYDRATION_ATTR_NAME_LOWER_CASE = SKIP_HYDRATION_ATTR_NAME.toLowerCase();
- const attrs = tNode.mergedAttrs;
- if (attrs === null)
- return false;
- // only ever look at the attribute name and skip the values
- for (let i = 0; i < attrs.length; i += 2) {
- const value = attrs[i];
- // This is a marker, which means that the static attributes section is over,
- // so we can exit early.
- if (typeof value === 'number')
- return false;
- if (typeof value === 'string' && value.toLowerCase() === SKIP_HYDRATION_ATTR_NAME_LOWER_CASE) {
- return true;
- }
- }
- return false;
- }
- /**
- * Helper function to check if a given RElement has the 'ngSkipHydration' attribute.
- */
- function hasSkipHydrationAttrOnRElement(rNode) {
- return rNode.hasAttribute(SKIP_HYDRATION_ATTR_NAME);
- }
- /**
- * Checks whether a TNode has a flag to indicate that it's a part of
- * a skip hydration block.
- */
- function hasInSkipHydrationBlockFlag(tNode) {
- return (tNode.flags & 128 /* TNodeFlags.inSkipHydrationBlock */) === 128 /* TNodeFlags.inSkipHydrationBlock */;
- }
- /**
- * Helper function that determines if a given node is within a skip hydration block
- * by navigating up the TNode tree to see if any parent nodes have skip hydration
- * attribute.
- *
- * TODO(akushnir): this function should contain the logic of `hasInSkipHydrationBlockFlag`,
- * there is no need to traverse parent nodes when we have a TNode flag (which would also
- * make this lookup O(1)).
- */
- function isInSkipHydrationBlock(tNode) {
- let currentTNode = tNode.parent;
- while (currentTNode) {
- if (hasSkipHydrationAttrOnTNode(currentTNode)) {
- return true;
- }
- currentTNode = currentTNode.parent;
- }
- return false;
- }
- /**
- * Flags for renderer-specific style modifiers.
- * @publicApi
- */
- var RendererStyleFlags2;
- (function (RendererStyleFlags2) {
- // TODO(misko): This needs to be refactored into a separate file so that it can be imported from
- // `node_manipulation.ts` Currently doing the import cause resolution order to change and fails
- // the tests. The work around is to have hard coded value in `node_manipulation.ts` for now.
- /**
- * Marks a style as important.
- */
- RendererStyleFlags2[RendererStyleFlags2["Important"] = 1] = "Important";
- /**
- * Marks a style as using dash case naming (this-is-dash-case).
- */
- RendererStyleFlags2[RendererStyleFlags2["DashCase"] = 2] = "DashCase";
- })(RendererStyleFlags2 || (RendererStyleFlags2 = {}));
- /**
- * Disallowed strings in the comment.
- *
- * see: https://html.spec.whatwg.org/multipage/syntax.html#comments
- */
- const COMMENT_DISALLOWED = /^>|^->|<!--|-->|--!>|<!-$/g;
- /**
- * Delimiter in the disallowed strings which needs to be wrapped with zero with character.
- */
- const COMMENT_DELIMITER = /(<|>)/g;
- const COMMENT_DELIMITER_ESCAPED = '\u200B$1\u200B';
- /**
- * Escape the content of comment strings so that it can be safely inserted into a comment node.
- *
- * The issue is that HTML does not specify any way to escape comment end text inside the comment.
- * Consider: `<!-- The way you close a comment is with ">", and "->" at the beginning or by "-->" or
- * "--!>" at the end. -->`. Above the `"-->"` is meant to be text not an end to the comment. This
- * can be created programmatically through DOM APIs. (`<!--` are also disallowed.)
- *
- * see: https://html.spec.whatwg.org/multipage/syntax.html#comments
- *
- * ```
- * div.innerHTML = div.innerHTML
- * ```
- *
- * One would expect that the above code would be safe to do, but it turns out that because comment
- * text is not escaped, the comment may contain text which will prematurely close the comment
- * opening up the application for XSS attack. (In SSR we programmatically create comment nodes which
- * may contain such text and expect them to be safe.)
- *
- * This function escapes the comment text by looking for comment delimiters (`<` and `>`) and
- * surrounding them with `_>_` where the `_` is a zero width space `\u200B`. The result is that if a
- * comment contains any of the comment start/end delimiters (such as `<!--`, `-->` or `--!>`) the
- * text it will render normally but it will not cause the HTML parser to close/open the comment.
- *
- * @param value text to make safe for comment node by escaping the comment open/close character
- * sequence.
- */
- function escapeCommentText(value) {
- return value.replace(COMMENT_DISALLOWED, (text) => text.replace(COMMENT_DELIMITER, COMMENT_DELIMITER_ESCAPED));
- }
- // Keeps track of the currently-active LViews.
- const TRACKED_LVIEWS = new Map();
- // Used for generating unique IDs for LViews.
- let uniqueIdCounter = 0;
- /** Gets a unique ID that can be assigned to an LView. */
- function getUniqueLViewId() {
- return uniqueIdCounter++;
- }
- /** Starts tracking an LView. */
- function registerLView(lView) {
- ngDevMode && assertNumber(lView[ID], 'LView must have an ID in order to be registered');
- TRACKED_LVIEWS.set(lView[ID], lView);
- }
- /** Gets an LView by its unique ID. */
- function getLViewById(id) {
- ngDevMode && assertNumber(id, 'ID used for LView lookup must be a number');
- return TRACKED_LVIEWS.get(id) || null;
- }
- /** Stops tracking an LView. */
- function unregisterLView(lView) {
- ngDevMode && assertNumber(lView[ID], 'Cannot stop tracking an LView that does not have an ID');
- TRACKED_LVIEWS.delete(lView[ID]);
- }
- /**
- * The internal view context which is specific to a given DOM element, directive or
- * component instance. Each value in here (besides the LView and element node details)
- * can be present, null or undefined. If undefined then it implies the value has not been
- * looked up yet, otherwise, if null, then a lookup was executed and nothing was found.
- *
- * Each value will get filled when the respective value is examined within the getContext
- * function. The component, element and each directive instance will share the same instance
- * of the context.
- */
- class LContext {
- /** Component's parent view data. */
- get lView() {
- return getLViewById(this.lViewId);
- }
- constructor(
- /**
- * ID of the component's parent view data.
- */
- lViewId,
- /**
- * The index instance of the node.
- */
- nodeIndex,
- /**
- * The instance of the DOM node that is attached to the lNode.
- */
- native) {
- this.lViewId = lViewId;
- this.nodeIndex = nodeIndex;
- this.native = native;
- }
- }
- /**
- * Returns the matching `LContext` data for a given DOM node, directive or component instance.
- *
- * This function will examine the provided DOM element, component, or directive instance\'s
- * monkey-patched property to derive the `LContext` data. Once called then the monkey-patched
- * value will be that of the newly created `LContext`.
- *
- * If the monkey-patched value is the `LView` instance then the context value for that
- * target will be created and the monkey-patch reference will be updated. Therefore when this
- * function is called it may mutate the provided element\'s, component\'s or any of the associated
- * directive\'s monkey-patch values.
- *
- * If the monkey-patch value is not detected then the code will walk up the DOM until an element
- * is found which contains a monkey-patch reference. When that occurs then the provided element
- * will be updated with a new context (which is then returned). If the monkey-patch value is not
- * detected for a component/directive instance then it will throw an error (all components and
- * directives should be automatically monkey-patched by ivy).
- *
- * @param target Component, Directive or DOM Node.
- */
- function getLContext(target) {
- let mpValue = readPatchedData(target);
- if (mpValue) {
- // only when it's an array is it considered an LView instance
- // ... otherwise it's an already constructed LContext instance
- if (isLView(mpValue)) {
- const lView = mpValue;
- let nodeIndex;
- let component = undefined;
- let directives = undefined;
- if (isComponentInstance(target)) {
- nodeIndex = findViaComponent(lView, target);
- if (nodeIndex == -1) {
- throw new Error('The provided component was not found in the application');
- }
- component = target;
- }
- else if (isDirectiveInstance(target)) {
- nodeIndex = findViaDirective(lView, target);
- if (nodeIndex == -1) {
- throw new Error('The provided directive was not found in the application');
- }
- directives = getDirectivesAtNodeIndex(nodeIndex, lView);
- }
- else {
- nodeIndex = findViaNativeElement(lView, target);
- if (nodeIndex == -1) {
- return null;
- }
- }
- // the goal is not to fill the entire context full of data because the lookups
- // are expensive. Instead, only the target data (the element, component, container, ICU
- // expression or directive details) are filled into the context. If called multiple times
- // with different target values then the missing target data will be filled in.
- const native = unwrapRNode(lView[nodeIndex]);
- const existingCtx = readPatchedData(native);
- const context = (existingCtx && !Array.isArray(existingCtx)) ?
- existingCtx :
- createLContext(lView, nodeIndex, native);
- // only when the component has been discovered then update the monkey-patch
- if (component && context.component === undefined) {
- context.component = component;
- attachPatchData(context.component, context);
- }
- // only when the directives have been discovered then update the monkey-patch
- if (directives && context.directives === undefined) {
- context.directives = directives;
- for (let i = 0; i < directives.length; i++) {
- attachPatchData(directives[i], context);
- }
- }
- attachPatchData(context.native, context);
- mpValue = context;
- }
- }
- else {
- const rElement = target;
- ngDevMode && assertDomNode(rElement);
- // if the context is not found then we need to traverse upwards up the DOM
- // to find the nearest element that has already been monkey patched with data
- let parent = rElement;
- while (parent = parent.parentNode) {
- const parentContext = readPatchedData(parent);
- if (parentContext) {
- const lView = Array.isArray(parentContext) ? parentContext : parentContext.lView;
- // the edge of the app was also reached here through another means
- // (maybe because the DOM was changed manually).
- if (!lView) {
- return null;
- }
- const index = findViaNativeElement(lView, rElement);
- if (index >= 0) {
- const native = unwrapRNode(lView[index]);
- const context = createLContext(lView, index, native);
- attachPatchData(native, context);
- mpValue = context;
- break;
- }
- }
- }
- }
- return mpValue || null;
- }
- /**
- * Creates an empty instance of a `LContext` context
- */
- function createLContext(lView, nodeIndex, native) {
- return new LContext(lView[ID], nodeIndex, native);
- }
- /**
- * Takes a component instance and returns the view for that component.
- *
- * @param componentInstance
- * @returns The component's view
- */
- function getComponentViewByInstance(componentInstance) {
- let patchedData = readPatchedData(componentInstance);
- let lView;
- if (isLView(patchedData)) {
- const contextLView = patchedData;
- const nodeIndex = findViaComponent(contextLView, componentInstance);
- lView = getComponentLViewByIndex(nodeIndex, contextLView);
- const context = createLContext(contextLView, nodeIndex, lView[HOST]);
- context.component = componentInstance;
- attachPatchData(componentInstance, context);
- attachPatchData(context.native, context);
- }
- else {
- const context = patchedData;
- const contextLView = context.lView;
- ngDevMode && assertLView(contextLView);
- lView = getComponentLViewByIndex(context.nodeIndex, contextLView);
- }
- return lView;
- }
- /**
- * This property will be monkey-patched on elements, components and directives.
- */
- const MONKEY_PATCH_KEY_NAME = '__ngContext__';
- /**
- * Assigns the given data to the given target (which could be a component,
- * directive or DOM node instance) using monkey-patching.
- */
- function attachPatchData(target, data) {
- ngDevMode && assertDefined(target, 'Target expected');
- // Only attach the ID of the view in order to avoid memory leaks (see #41047). We only do this
- // for `LView`, because we have control over when an `LView` is created and destroyed, whereas
- // we can't know when to remove an `LContext`.
- if (isLView(data)) {
- target[MONKEY_PATCH_KEY_NAME] = data[ID];
- registerLView(data);
- }
- else {
- target[MONKEY_PATCH_KEY_NAME] = data;
- }
- }
- /**
- * Returns the monkey-patch value data present on the target (which could be
- * a component, directive or a DOM node).
- */
- function readPatchedData(target) {
- ngDevMode && assertDefined(target, 'Target expected');
- const data = target[MONKEY_PATCH_KEY_NAME];
- return (typeof data === 'number') ? getLViewById(data) : data || null;
- }
- function readPatchedLView(target) {
- const value = readPatchedData(target);
- if (value) {
- return (isLView(value) ? value : value.lView);
- }
- return null;
- }
- function isComponentInstance(instance) {
- return instance && instance.constructor && instance.constructor.ɵcmp;
- }
- function isDirectiveInstance(instance) {
- return instance && instance.constructor && instance.constructor.ɵdir;
- }
- /**
- * Locates the element within the given LView and returns the matching index
- */
- function findViaNativeElement(lView, target) {
- const tView = lView[TVIEW];
- for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
- if (unwrapRNode(lView[i]) === target) {
- return i;
- }
- }
- return -1;
- }
- /**
- * Locates the next tNode (child, sibling or parent).
- */
- function traverseNextElement(tNode) {
- if (tNode.child) {
- return tNode.child;
- }
- else if (tNode.next) {
- return tNode.next;
- }
- else {
- // Let's take the following template: <div><span>text</span></div><component/>
- // After checking the text node, we need to find the next parent that has a "next" TNode,
- // in this case the parent `div`, so that we can find the component.
- while (tNode.parent && !tNode.parent.next) {
- tNode = tNode.parent;
- }
- return tNode.parent && tNode.parent.next;
- }
- }
- /**
- * Locates the component within the given LView and returns the matching index
- */
- function findViaComponent(lView, componentInstance) {
- const componentIndices = lView[TVIEW].components;
- if (componentIndices) {
- for (let i = 0; i < componentIndices.length; i++) {
- const elementComponentIndex = componentIndices[i];
- const componentView = getComponentLViewByIndex(elementComponentIndex, lView);
- if (componentView[CONTEXT] === componentInstance) {
- return elementComponentIndex;
- }
- }
- }
- else {
- const rootComponentView = getComponentLViewByIndex(HEADER_OFFSET, lView);
- const rootComponent = rootComponentView[CONTEXT];
- if (rootComponent === componentInstance) {
- // we are dealing with the root element here therefore we know that the
- // element is the very first element after the HEADER data in the lView
- return HEADER_OFFSET;
- }
- }
- return -1;
- }
- /**
- * Locates the directive within the given LView and returns the matching index
- */
- function findViaDirective(lView, directiveInstance) {
- // if a directive is monkey patched then it will (by default)
- // have a reference to the LView of the current view. The
- // element bound to the directive being search lives somewhere
- // in the view data. We loop through the nodes and check their
- // list of directives for the instance.
- let tNode = lView[TVIEW].firstChild;
- while (tNode) {
- const directiveIndexStart = tNode.directiveStart;
- const directiveIndexEnd = tNode.directiveEnd;
- for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
- if (lView[i] === directiveInstance) {
- return tNode.index;
- }
- }
- tNode = traverseNextElement(tNode);
- }
- return -1;
- }
- /**
- * Returns a list of directives applied to a node at a specific index. The list includes
- * directives matched by selector and any host directives, but it excludes components.
- * Use `getComponentAtNodeIndex` to find the component applied to a node.
- *
- * @param nodeIndex The node index
- * @param lView The target view data
- */
- function getDirectivesAtNodeIndex(nodeIndex, lView) {
- const tNode = lView[TVIEW].data[nodeIndex];
- if (tNode.directiveStart === 0)
- return EMPTY_ARRAY;
- const results = [];
- for (let i = tNode.directiveStart; i < tNode.directiveEnd; i++) {
- const directiveInstance = lView[i];
- if (!isComponentInstance(directiveInstance)) {
- results.push(directiveInstance);
- }
- }
- return results;
- }
- function getComponentAtNodeIndex(nodeIndex, lView) {
- const tNode = lView[TVIEW].data[nodeIndex];
- const { directiveStart, componentOffset } = tNode;
- return componentOffset > -1 ? lView[directiveStart + componentOffset] : null;
- }
- /**
- * Returns a map of local references (local reference name => element or directive instance) that
- * exist on a given element.
- */
- function discoverLocalRefs(lView, nodeIndex) {
- const tNode = lView[TVIEW].data[nodeIndex];
- if (tNode && tNode.localNames) {
- const result = {};
- let localIndex = tNode.index + 1;
- for (let i = 0; i < tNode.localNames.length; i += 2) {
- result[tNode.localNames[i]] = lView[localIndex];
- localIndex++;
- }
- return result;
- }
- return null;
- }
- let _icuContainerIterate;
- /**
- * Iterator which provides ability to visit all of the `TIcuContainerNode` root `RNode`s.
- */
- function icuContainerIterate(tIcuContainerNode, lView) {
- return _icuContainerIterate(tIcuContainerNode, lView);
- }
- /**
- * Ensures that `IcuContainerVisitor`'s implementation is present.
- *
- * This function is invoked when i18n instruction comes across an ICU. The purpose is to allow the
- * bundler to tree shake ICU logic and only load it if ICU instruction is executed.
- */
- function ensureIcuContainerVisitorLoaded(loader) {
- if (_icuContainerIterate === undefined) {
- // Do not inline this function. We want to keep `ensureIcuContainerVisitorLoaded` light, so it
- // can be inlined into call-site.
- _icuContainerIterate = loader();
- }
- }
- /**
- * Gets the parent LView of the passed LView, if the PARENT is an LContainer, will get the parent of
- * that LContainer, which is an LView
- * @param lView the lView whose parent to get
- */
- function getLViewParent(lView) {
- ngDevMode && assertLView(lView);
- const parent = lView[PARENT];
- return isLContainer(parent) ? parent[PARENT] : parent;
- }
- /**
- * Retrieve the root view from any component or `LView` by walking the parent `LView` until
- * reaching the root `LView`.
- *
- * @param componentOrLView any component or `LView`
- */
- function getRootView(componentOrLView) {
- ngDevMode && assertDefined(componentOrLView, 'component');
- let lView = isLView(componentOrLView) ? componentOrLView : readPatchedLView(componentOrLView);
- while (lView && !(lView[FLAGS] & 512 /* LViewFlags.IsRoot */)) {
- lView = getLViewParent(lView);
- }
- ngDevMode && assertLView(lView);
- return lView;
- }
- /**
- * Returns the context information associated with the application where the target is situated. It
- * does this by walking the parent views until it gets to the root view, then getting the context
- * off of that.
- *
- * @param viewOrComponent the `LView` or component to get the root context for.
- */
- function getRootContext(viewOrComponent) {
- const rootView = getRootView(viewOrComponent);
- ngDevMode &&
- assertDefined(rootView[CONTEXT], 'Root view has no context. Perhaps it is disconnected?');
- return rootView[CONTEXT];
- }
- /**
- * Gets the first `LContainer` in the LView or `null` if none exists.
- */
- function getFirstLContainer(lView) {
- return getNearestLContainer(lView[CHILD_HEAD]);
- }
- /**
- * Gets the next `LContainer` that is a sibling of the given container.
- */
- function getNextLContainer(container) {
- return getNearestLContainer(container[NEXT]);
- }
- function getNearestLContainer(viewOrContainer) {
- while (viewOrContainer !== null && !isLContainer(viewOrContainer)) {
- viewOrContainer = viewOrContainer[NEXT];
- }
- return viewOrContainer;
- }
- /**
- * NOTE: for performance reasons, the possible actions are inlined within the function instead of
- * being passed as an argument.
- */
- function applyToElementOrContainer(action, renderer, parent, lNodeToHandle, beforeNode) {
- // If this slot was allocated for a text node dynamically created by i18n, the text node itself
- // won't be created until i18nApply() in the update block, so this node should be skipped.
- // For more info, see "ICU expressions should work inside an ngTemplateOutlet inside an ngFor"
- // in `i18n_spec.ts`.
- if (lNodeToHandle != null) {
- let lContainer;
- let isComponent = false;
- // We are expecting an RNode, but in the case of a component or LContainer the `RNode` is
- // wrapped in an array which needs to be unwrapped. We need to know if it is a component and if
- // it has LContainer so that we can process all of those cases appropriately.
- if (isLContainer(lNodeToHandle)) {
- lContainer = lNodeToHandle;
- }
- else if (isLView(lNodeToHandle)) {
- isComponent = true;
- ngDevMode && assertDefined(lNodeToHandle[HOST], 'HOST must be defined for a component LView');
- lNodeToHandle = lNodeToHandle[HOST];
- }
- const rNode = unwrapRNode(lNodeToHandle);
- if (action === 0 /* WalkTNodeTreeAction.Create */ && parent !== null) {
- if (beforeNode == null) {
- nativeAppendChild(renderer, parent, rNode);
- }
- else {
- nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true);
- }
- }
- else if (action === 1 /* WalkTNodeTreeAction.Insert */ && parent !== null) {
- nativeInsertBefore(renderer, parent, rNode, beforeNode || null, true);
- }
- else if (action === 2 /* WalkTNodeTreeAction.Detach */) {
- nativeRemoveNode(renderer, rNode, isComponent);
- }
- else if (action === 3 /* WalkTNodeTreeAction.Destroy */) {
- ngDevMode && ngDevMode.rendererDestroyNode++;
- renderer.destroyNode(rNode);
- }
- if (lContainer != null) {
- applyContainer(renderer, action, lContainer, parent, beforeNode);
- }
- }
- }
- function createTextNode(renderer, value) {
- ngDevMode && ngDevMode.rendererCreateTextNode++;
- ngDevMode && ngDevMode.rendererSetText++;
- return renderer.createText(value);
- }
- function updateTextNode(renderer, rNode, value) {
- ngDevMode && ngDevMode.rendererSetText++;
- renderer.setValue(rNode, value);
- }
- function createCommentNode(renderer, value) {
- ngDevMode && ngDevMode.rendererCreateComment++;
- return renderer.createComment(escapeCommentText(value));
- }
- /**
- * Creates a native element from a tag name, using a renderer.
- * @param renderer A renderer to use
- * @param name the tag name
- * @param namespace Optional namespace for element.
- * @returns the element created
- */
- function createElementNode(renderer, name, namespace) {
- ngDevMode && ngDevMode.rendererCreateElement++;
- return renderer.createElement(name, namespace);
- }
- /**
- * Removes all DOM elements associated with a view.
- *
- * Because some root nodes of the view may be containers, we sometimes need
- * to propagate deeply into the nested containers to remove all elements in the
- * views beneath it.
- *
- * @param tView The `TView' of the `LView` from which elements should be added or removed
- * @param lView The view from which elements should be added or removed
- */
- function removeViewFromDOM(tView, lView) {
- const renderer = lView[RENDERER];
- applyView(tView, lView, renderer, 2 /* WalkTNodeTreeAction.Detach */, null, null);
- lView[HOST] = null;
- lView[T_HOST] = null;
- }
- /**
- * Adds all DOM elements associated with a view.
- *
- * Because some root nodes of the view may be containers, we sometimes need
- * to propagate deeply into the nested containers to add all elements in the
- * views beneath it.
- *
- * @param tView The `TView' of the `LView` from which elements should be added or removed
- * @param parentTNode The `TNode` where the `LView` should be attached to.
- * @param renderer Current renderer to use for DOM manipulations.
- * @param lView The view from which elements should be added or removed
- * @param parentNativeNode The parent `RElement` where it should be inserted into.
- * @param beforeNode The node before which elements should be added, if insert mode
- */
- function addViewToDOM(tView, parentTNode, renderer, lView, parentNativeNode, beforeNode) {
- lView[HOST] = parentNativeNode;
- lView[T_HOST] = parentTNode;
- applyView(tView, lView, renderer, 1 /* WalkTNodeTreeAction.Insert */, parentNativeNode, beforeNode);
- }
- /**
- * Detach a `LView` from the DOM by detaching its nodes.
- *
- * @param tView The `TView' of the `LView` to be detached
- * @param lView the `LView` to be detached.
- */
- function detachViewFromDOM(tView, lView) {
- applyView(tView, lView, lView[RENDERER], 2 /* WalkTNodeTreeAction.Detach */, null, null);
- }
- /**
- * Traverses down and up the tree of views and containers to remove listeners and
- * call onDestroy callbacks.
- *
- * Notes:
- * - Because it's used for onDestroy calls, it needs to be bottom-up.
- * - Must process containers instead of their views to avoid splicing
- * when views are destroyed and re-added.
- * - Using a while loop because it's faster than recursion
- * - Destroy only called on movement to sibling or movement to parent (laterally or up)
- *
- * @param rootView The view to destroy
- */
- function destroyViewTree(rootView) {
- // If the view has no children, we can clean it up and return early.
- let lViewOrLContainer = rootView[CHILD_HEAD];
- if (!lViewOrLContainer) {
- return cleanUpView(rootView[TVIEW], rootView);
- }
- while (lViewOrLContainer) {
- let next = null;
- if (isLView(lViewOrLContainer)) {
- // If LView, traverse down to child.
- next = lViewOrLContainer[CHILD_HEAD];
- }
- else {
- ngDevMode && assertLContainer(lViewOrLContainer);
- // If container, traverse down to its first LView.
- const firstView = lViewOrLContainer[CONTAINER_HEADER_OFFSET];
- if (firstView)
- next = firstView;
- }
- if (!next) {
- // Only clean up view when moving to the side or up, as destroy hooks
- // should be called in order from the bottom up.
- while (lViewOrLContainer && !lViewOrLContainer[NEXT] && lViewOrLContainer !== rootView) {
- if (isLView(lViewOrLContainer)) {
- cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer);
- }
- lViewOrLContainer = lViewOrLContainer[PARENT];
- }
- if (lViewOrLContainer === null)
- lViewOrLContainer = rootView;
- if (isLView(lViewOrLContainer)) {
- cleanUpView(lViewOrLContainer[TVIEW], lViewOrLContainer);
- }
- next = lViewOrLContainer && lViewOrLContainer[NEXT];
- }
- lViewOrLContainer = next;
- }
- }
- /**
- * Inserts a view into a container.
- *
- * This adds the view to the container's array of active views in the correct
- * position. It also adds the view's elements to the DOM if the container isn't a
- * root node of another view (in that case, the view's elements will be added when
- * the container's parent view is added later).
- *
- * @param tView The `TView' of the `LView` to insert
- * @param lView The view to insert
- * @param lContainer The container into which the view should be inserted
- * @param index Which index in the container to insert the child view into
- */
- function insertView(tView, lView, lContainer, index) {
- ngDevMode && assertLView(lView);
- ngDevMode && assertLContainer(lContainer);
- const indexInContainer = CONTAINER_HEADER_OFFSET + index;
- const containerLength = lContainer.length;
- if (index > 0) {
- // This is a new view, we need to add it to the children.
- lContainer[indexInContainer - 1][NEXT] = lView;
- }
- if (index < containerLength - CONTAINER_HEADER_OFFSET) {
- lView[NEXT] = lContainer[indexInContainer];
- addToArray(lContainer, CONTAINER_HEADER_OFFSET + index, lView);
- }
- else {
- lContainer.push(lView);
- lView[NEXT] = null;
- }
- lView[PARENT] = lContainer;
- // track views where declaration and insertion points are different
- const declarationLContainer = lView[DECLARATION_LCONTAINER];
- if (declarationLContainer !== null && lContainer !== declarationLContainer) {
- trackMovedView(declarationLContainer, lView);
- }
- // notify query that a new view has been added
- const lQueries = lView[QUERIES];
- if (lQueries !== null) {
- lQueries.insertView(tView);
- }
- // Sets the attached flag
- lView[FLAGS] |= 128 /* LViewFlags.Attached */;
- }
- /**
- * Track views created from the declaration container (TemplateRef) and inserted into a
- * different LContainer.
- */
- function trackMovedView(declarationContainer, lView) {
- ngDevMode && assertDefined(lView, 'LView required');
- ngDevMode && assertLContainer(declarationContainer);
- const movedViews = declarationContainer[MOVED_VIEWS];
- const insertedLContainer = lView[PARENT];
- ngDevMode && assertLContainer(insertedLContainer);
- const insertedComponentLView = insertedLContainer[PARENT][DECLARATION_COMPONENT_VIEW];
- ngDevMode && assertDefined(insertedComponentLView, 'Missing insertedComponentLView');
- const declaredComponentLView = lView[DECLARATION_COMPONENT_VIEW];
- ngDevMode && assertDefined(declaredComponentLView, 'Missing declaredComponentLView');
- if (declaredComponentLView !== insertedComponentLView) {
- // At this point the declaration-component is not same as insertion-component; this means that
- // this is a transplanted view. Mark the declared lView as having transplanted views so that
- // those views can participate in CD.
- declarationContainer[HAS_TRANSPLANTED_VIEWS] = true;
- }
- if (movedViews === null) {
- declarationContainer[MOVED_VIEWS] = [lView];
- }
- else {
- movedViews.push(lView);
- }
- }
- function detachMovedView(declarationContainer, lView) {
- ngDevMode && assertLContainer(declarationContainer);
- ngDevMode &&
- assertDefined(declarationContainer[MOVED_VIEWS], 'A projected view should belong to a non-empty projected views collection');
- const movedViews = declarationContainer[MOVED_VIEWS];
- const declarationViewIndex = movedViews.indexOf(lView);
- const insertionLContainer = lView[PARENT];
- ngDevMode && assertLContainer(insertionLContainer);
- // If the view was marked for refresh but then detached before it was checked (where the flag
- // would be cleared and the counter decremented), we need to update the status here.
- clearViewRefreshFlag(lView);
- movedViews.splice(declarationViewIndex, 1);
- }
- /**
- * Detaches a view from a container.
- *
- * This method removes the view from the container's array of active views. It also
- * removes the view's elements from the DOM.
- *
- * @param lContainer The container from which to detach a view
- * @param removeIndex The index of the view to detach
- * @returns Detached LView instance.
- */
- function detachView(lContainer, removeIndex) {
- if (lContainer.length <= CONTAINER_HEADER_OFFSET)
- return;
- const indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex;
- const viewToDetach = lContainer[indexInContainer];
- if (viewToDetach) {
- const declarationLContainer = viewToDetach[DECLARATION_LCONTAINER];
- if (declarationLContainer !== null && declarationLContainer !== lContainer) {
- detachMovedView(declarationLContainer, viewToDetach);
- }
- if (removeIndex > 0) {
- lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT];
- }
- const removedLView = removeFromArray(lContainer, CONTAINER_HEADER_OFFSET + removeIndex);
- removeViewFromDOM(viewToDetach[TVIEW], viewToDetach);
- // notify query that a view has been removed
- const lQueries = removedLView[QUERIES];
- if (lQueries !== null) {
- lQueries.detachView(removedLView[TVIEW]);
- }
- viewToDetach[PARENT] = null;
- viewToDetach[NEXT] = null;
- // Unsets the attached flag
- viewToDetach[FLAGS] &= ~128 /* LViewFlags.Attached */;
- }
- return viewToDetach;
- }
- /**
- * A standalone function which destroys an LView,
- * conducting clean up (e.g. removing listeners, calling onDestroys).
- *
- * @param tView The `TView' of the `LView` to be destroyed
- * @param lView The view to be destroyed.
- */
- function destroyLView(tView, lView) {
- if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) {
- const renderer = lView[RENDERER];
- lView[REACTIVE_TEMPLATE_CONSUMER] && consumerDestroy(lView[REACTIVE_TEMPLATE_CONSUMER]);
- lView[REACTIVE_HOST_BINDING_CONSUMER] && consumerDestroy(lView[REACTIVE_HOST_BINDING_CONSUMER]);
- if (renderer.destroyNode) {
- applyView(tView, lView, renderer, 3 /* WalkTNodeTreeAction.Destroy */, null, null);
- }
- destroyViewTree(lView);
- }
- }
- /**
- * Calls onDestroys hooks for all directives and pipes in a given view and then removes all
- * listeners. Listeners are removed as the last step so events delivered in the onDestroys hooks
- * can be propagated to @Output listeners.
- *
- * @param tView `TView` for the `LView` to clean up.
- * @param lView The LView to clean up
- */
- function cleanUpView(tView, lView) {
- if (!(lView[FLAGS] & 256 /* LViewFlags.Destroyed */)) {
- // Usually the Attached flag is removed when the view is detached from its parent, however
- // if it's a root view, the flag won't be unset hence why we're also removing on destroy.
- lView[FLAGS] &= ~128 /* LViewFlags.Attached */;
- // Mark the LView as destroyed *before* executing the onDestroy hooks. An onDestroy hook
- // runs arbitrary user code, which could include its own `viewRef.destroy()` (or similar). If
- // We don't flag the view as destroyed before the hooks, this could lead to an infinite loop.
- // This also aligns with the ViewEngine behavior. It also means that the onDestroy hook is
- // really more of an "afterDestroy" hook if you think about it.
- lView[FLAGS] |= 256 /* LViewFlags.Destroyed */;
- executeOnDestroys(tView, lView);
- processCleanups(tView, lView);
- // For component views only, the local renderer is destroyed at clean up time.
- if (lView[TVIEW].type === 1 /* TViewType.Component */) {
- ngDevMode && ngDevMode.rendererDestroy++;
- lView[RENDERER].destroy();
- }
- const declarationContainer = lView[DECLARATION_LCONTAINER];
- // we are dealing with an embedded view that is still inserted into a container
- if (declarationContainer !== null && isLContainer(lView[PARENT])) {
- // and this is a projected view
- if (declarationContainer !== lView[PARENT]) {
- detachMovedView(declarationContainer, lView);
- }
- // For embedded views still attached to a container: remove query result from this view.
- const lQueries = lView[QUERIES];
- if (lQueries !== null) {
- lQueries.detachView(tView);
- }
- }
- // Unregister the view once everything else has been cleaned up.
- unregisterLView(lView);
- }
- }
- /** Removes listeners and unsubscribes from output subscriptions */
- function processCleanups(tView, lView) {
- const tCleanup = tView.cleanup;
- const lCleanup = lView[CLEANUP];
- if (tCleanup !== null) {
- for (let i = 0; i < tCleanup.length - 1; i += 2) {
- if (typeof tCleanup[i] === 'string') {
- // This is a native DOM listener. It will occupy 4 entries in the TCleanup array (hence i +=
- // 2 at the end of this block).
- const targetIdx = tCleanup[i + 3];
- ngDevMode && assertNumber(targetIdx, 'cleanup target must be a number');
- if (targetIdx >= 0) {
- // unregister
- lCleanup[targetIdx]();
- }
- else {
- // Subscription
- lCleanup[-targetIdx].unsubscribe();
- }
- i += 2;
- }
- else {
- // This is a cleanup function that is grouped with the index of its context
- const context = lCleanup[tCleanup[i + 1]];
- tCleanup[i].call(context);
- }
- }
- }
- if (lCleanup !== null) {
- lView[CLEANUP] = null;
- }
- const destroyHooks = lView[ON_DESTROY_HOOKS];
- if (destroyHooks !== null) {
- // Reset the ON_DESTROY_HOOKS array before iterating over it to prevent hooks that unregister
- // themselves from mutating the array during iteration.
- lView[ON_DESTROY_HOOKS] = null;
- for (let i = 0; i < destroyHooks.length; i++) {
- const destroyHooksFn = destroyHooks[i];
- ngDevMode && assertFunction(destroyHooksFn, 'Expecting destroy hook to be a function.');
- destroyHooksFn();
- }
- }
- }
- /** Calls onDestroy hooks for this view */
- function executeOnDestroys(tView, lView) {
- let destroyHooks;
- if (tView != null && (destroyHooks = tView.destroyHooks) != null) {
- for (let i = 0; i < destroyHooks.length; i += 2) {
- const context = lView[destroyHooks[i]];
- // Only call the destroy hook if the context has been requested.
- if (!(context instanceof NodeInjectorFactory)) {
- const toCall = destroyHooks[i + 1];
- if (Array.isArray(toCall)) {
- for (let j = 0; j < toCall.length; j += 2) {
- const callContext = context[toCall[j]];
- const hook = toCall[j + 1];
- profiler(4 /* ProfilerEvent.LifecycleHookStart */, callContext, hook);
- try {
- hook.call(callContext);
- }
- finally {
- profiler(5 /* ProfilerEvent.LifecycleHookEnd */, callContext, hook);
- }
- }
- }
- else {
- profiler(4 /* ProfilerEvent.LifecycleHookStart */, context, toCall);
- try {
- toCall.call(context);
- }
- finally {
- profiler(5 /* ProfilerEvent.LifecycleHookEnd */, context, toCall);
- }
- }
- }
- }
- }
- }
- /**
- * Returns a native element if a node can be inserted into the given parent.
- *
- * There are two reasons why we may not be able to insert a element immediately.
- * - Projection: When creating a child content element of a component, we have to skip the
- * insertion because the content of a component will be projected.
- * `<component><content>delayed due to projection</content></component>`
- * - Parent container is disconnected: This can happen when we are inserting a view into
- * parent container, which itself is disconnected. For example the parent container is part
- * of a View which has not be inserted or is made for projection but has not been inserted
- * into destination.
- *
- * @param tView: Current `TView`.
- * @param tNode: `TNode` for which we wish to retrieve render parent.
- * @param lView: Current `LView`.
- */
- function getParentRElement(tView, tNode, lView) {
- return getClosestRElement(tView, tNode.parent, lView);
- }
- /**
- * Get closest `RElement` or `null` if it can't be found.
- *
- * If `TNode` is `TNodeType.Element` => return `RElement` at `LView[tNode.index]` location.
- * If `TNode` is `TNodeType.ElementContainer|IcuContain` => return the parent (recursively).
- * If `TNode` is `null` then return host `RElement`:
- * - return `null` if projection
- * - return `null` if parent container is disconnected (we have no parent.)
- *
- * @param tView: Current `TView`.
- * @param tNode: `TNode` for which we wish to retrieve `RElement` (or `null` if host element is
- * needed).
- * @param lView: Current `LView`.
- * @returns `null` if the `RElement` can't be determined at this time (no parent / projection)
- */
- function getClosestRElement(tView, tNode, lView) {
- let parentTNode = tNode;
- // Skip over element and ICU containers as those are represented by a comment node and
- // can't be used as a render parent.
- while (parentTNode !== null &&
- (parentTNode.type & (8 /* TNodeType.ElementContainer */ | 32 /* TNodeType.Icu */))) {
- tNode = parentTNode;
- parentTNode = tNode.parent;
- }
- // If the parent tNode is null, then we are inserting across views: either into an embedded view
- // or a component view.
- if (parentTNode === null) {
- // We are inserting a root element of the component view into the component host element and
- // it should always be eager.
- return lView[HOST];
- }
- else {
- ngDevMode && assertTNodeType(parentTNode, 3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */);
- const { componentOffset } = parentTNode;
- if (componentOffset > -1) {
- ngDevMode && assertTNodeForLView(parentTNode, lView);
- const { encapsulation } = tView.data[parentTNode.directiveStart + componentOffset];
- // We've got a parent which is an element in the current view. We just need to verify if the
- // parent element is not a component. Component's content nodes are not inserted immediately
- // because they will be projected, and so doing insert at this point would be wasteful.
- // Since the projection would then move it to its final destination. Note that we can't
- // make this assumption when using the Shadow DOM, because the native projection placeholders
- // (<content> or <slot>) have to be in place as elements are being inserted.
- if (encapsulation === ViewEncapsulation.None ||
- encapsulation === ViewEncapsulation.Emulated) {
- return null;
- }
- }
- return getNativeByTNode(parentTNode, lView);
- }
- }
- /**
- * Inserts a native node before another native node for a given parent.
- * This is a utility function that can be used when native nodes were determined.
- */
- function nativeInsertBefore(renderer, parent, child, beforeNode, isMove) {
- ngDevMode && ngDevMode.rendererInsertBefore++;
- renderer.insertBefore(parent, child, beforeNode, isMove);
- }
- function nativeAppendChild(renderer, parent, child) {
- ngDevMode && ngDevMode.rendererAppendChild++;
- ngDevMode && assertDefined(parent, 'parent node must be defined');
- renderer.appendChild(parent, child);
- }
- function nativeAppendOrInsertBefore(renderer, parent, child, beforeNode, isMove) {
- if (beforeNode !== null) {
- nativeInsertBefore(renderer, parent, child, beforeNode, isMove);
- }
- else {
- nativeAppendChild(renderer, parent, child);
- }
- }
- /** Removes a node from the DOM given its native parent. */
- function nativeRemoveChild(renderer, parent, child, isHostElement) {
- renderer.removeChild(parent, child, isHostElement);
- }
- /** Checks if an element is a `<template>` node. */
- function isTemplateNode(node) {
- return node.tagName === 'TEMPLATE' && node.content !== undefined;
- }
- /**
- * Returns a native parent of a given native node.
- */
- function nativeParentNode(renderer, node) {
- return renderer.parentNode(node);
- }
- /**
- * Returns a native sibling of a given native node.
- */
- function nativeNextSibling(renderer, node) {
- return renderer.nextSibling(node);
- }
- /**
- * Find a node in front of which `currentTNode` should be inserted.
- *
- * This method determines the `RNode` in front of which we should insert the `currentRNode`. This
- * takes `TNode.insertBeforeIndex` into account if i18n code has been invoked.
- *
- * @param parentTNode parent `TNode`
- * @param currentTNode current `TNode` (The node which we would like to insert into the DOM)
- * @param lView current `LView`
- */
- function getInsertInFrontOfRNode(parentTNode, currentTNode, lView) {
- return _getInsertInFrontOfRNodeWithI18n(parentTNode, currentTNode, lView);
- }
- /**
- * Find a node in front of which `currentTNode` should be inserted. (Does not take i18n into
- * account)
- *
- * This method determines the `RNode` in front of which we should insert the `currentRNode`. This
- * does not take `TNode.insertBeforeIndex` into account.
- *
- * @param parentTNode parent `TNode`
- * @param currentTNode current `TNode` (The node which we would like to insert into the DOM)
- * @param lView current `LView`
- */
- function getInsertInFrontOfRNodeWithNoI18n(parentTNode, currentTNode, lView) {
- if (parentTNode.type & (8 /* TNodeType.ElementContainer */ | 32 /* TNodeType.Icu */)) {
- return getNativeByTNode(parentTNode, lView);
- }
- return null;
- }
- /**
- * Tree shakable boundary for `getInsertInFrontOfRNodeWithI18n` function.
- *
- * This function will only be set if i18n code runs.
- */
- let _getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithNoI18n;
- /**
- * Tree shakable boundary for `processI18nInsertBefore` function.
- *
- * This function will only be set if i18n code runs.
- */
- let _processI18nInsertBefore;
- function setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore) {
- _getInsertInFrontOfRNodeWithI18n = getInsertInFrontOfRNodeWithI18n;
- _processI18nInsertBefore = processI18nInsertBefore;
- }
- /**
- * Appends the `child` native node (or a collection of nodes) to the `parent`.
- *
- * @param tView The `TView' to be appended
- * @param lView The current LView
- * @param childRNode The native child (or children) that should be appended
- * @param childTNode The TNode of the child element
- */
- function appendChild(tView, lView, childRNode, childTNode) {
- const parentRNode = getParentRElement(tView, childTNode, lView);
- const renderer = lView[RENDERER];
- const parentTNode = childTNode.parent || lView[T_HOST];
- const anchorNode = getInsertInFrontOfRNode(parentTNode, childTNode, lView);
- if (parentRNode != null) {
- if (Array.isArray(childRNode)) {
- for (let i = 0; i < childRNode.length; i++) {
- nativeAppendOrInsertBefore(renderer, parentRNode, childRNode[i], anchorNode, false);
- }
- }
- else {
- nativeAppendOrInsertBefore(renderer, parentRNode, childRNode, anchorNode, false);
- }
- }
- _processI18nInsertBefore !== undefined &&
- _processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRNode);
- }
- /**
- * Returns the first native node for a given LView, starting from the provided TNode.
- *
- * Native nodes are returned in the order in which those appear in the native tree (DOM).
- */
- function getFirstNativeNode(lView, tNode) {
- if (tNode !== null) {
- ngDevMode &&
- assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 32 /* TNodeType.Icu */ | 16 /* TNodeType.Projection */);
- const tNodeType = tNode.type;
- if (tNodeType & 3 /* TNodeType.AnyRNode */) {
- return getNativeByTNode(tNode, lView);
- }
- else if (tNodeType & 4 /* TNodeType.Container */) {
- return getBeforeNodeForView(-1, lView[tNode.index]);
- }
- else if (tNodeType & 8 /* TNodeType.ElementContainer */) {
- const elIcuContainerChild = tNode.child;
- if (elIcuContainerChild !== null) {
- return getFirstNativeNode(lView, elIcuContainerChild);
- }
- else {
- const rNodeOrLContainer = lView[tNode.index];
- if (isLContainer(rNodeOrLContainer)) {
- return getBeforeNodeForView(-1, rNodeOrLContainer);
- }
- else {
- return unwrapRNode(rNodeOrLContainer);
- }
- }
- }
- else if (tNodeType & 32 /* TNodeType.Icu */) {
- let nextRNode = icuContainerIterate(tNode, lView);
- let rNode = nextRNode();
- // If the ICU container has no nodes, than we use the ICU anchor as the node.
- return rNode || unwrapRNode(lView[tNode.index]);
- }
- else {
- const projectionNodes = getProjectionNodes(lView, tNode);
- if (projectionNodes !== null) {
- if (Array.isArray(projectionNodes)) {
- return projectionNodes[0];
- }
- const parentView = getLViewParent(lView[DECLARATION_COMPONENT_VIEW]);
- ngDevMode && assertParentView(parentView);
- return getFirstNativeNode(parentView, projectionNodes);
- }
- else {
- return getFirstNativeNode(lView, tNode.next);
- }
- }
- }
- return null;
- }
- function getProjectionNodes(lView, tNode) {
- if (tNode !== null) {
- const componentView = lView[DECLARATION_COMPONENT_VIEW];
- const componentHost = componentView[T_HOST];
- const slotIdx = tNode.projection;
- ngDevMode && assertProjectionSlots(lView);
- return componentHost.projection[slotIdx];
- }
- return null;
- }
- function getBeforeNodeForView(viewIndexInContainer, lContainer) {
- const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
- if (nextViewIndex < lContainer.length) {
- const lView = lContainer[nextViewIndex];
- const firstTNodeOfView = lView[TVIEW].firstChild;
- if (firstTNodeOfView !== null) {
- return getFirstNativeNode(lView, firstTNodeOfView);
- }
- }
- return lContainer[NATIVE];
- }
- /**
- * Removes a native node itself using a given renderer. To remove the node we are looking up its
- * parent from the native tree as not all platforms / browsers support the equivalent of
- * node.remove().
- *
- * @param renderer A renderer to be used
- * @param rNode The native node that should be removed
- * @param isHostElement A flag indicating if a node to be removed is a host of a component.
- */
- function nativeRemoveNode(renderer, rNode, isHostElement) {
- ngDevMode && ngDevMode.rendererRemoveNode++;
- const nativeParent = nativeParentNode(renderer, rNode);
- if (nativeParent) {
- nativeRemoveChild(renderer, nativeParent, rNode, isHostElement);
- }
- }
- /**
- * Clears the contents of a given RElement.
- *
- * @param rElement the native RElement to be cleared
- */
- function clearElementContents(rElement) {
- rElement.textContent = '';
- }
- /**
- * Performs the operation of `action` on the node. Typically this involves inserting or removing
- * nodes on the LView or projection boundary.
- */
- function applyNodes(renderer, action, tNode, lView, parentRElement, beforeNode, isProjection) {
- while (tNode != null) {
- ngDevMode && assertTNodeForLView(tNode, lView);
- ngDevMode &&
- assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 16 /* TNodeType.Projection */ | 32 /* TNodeType.Icu */);
- const rawSlotValue = lView[tNode.index];
- const tNodeType = tNode.type;
- if (isProjection) {
- if (action === 0 /* WalkTNodeTreeAction.Create */) {
- rawSlotValue && attachPatchData(unwrapRNode(rawSlotValue), lView);
- tNode.flags |= 2 /* TNodeFlags.isProjected */;
- }
- }
- if ((tNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
- if (tNodeType & 8 /* TNodeType.ElementContainer */) {
- applyNodes(renderer, action, tNode.child, lView, parentRElement, beforeNode, false);
- applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
- }
- else if (tNodeType & 32 /* TNodeType.Icu */) {
- const nextRNode = icuContainerIterate(tNode, lView);
- let rNode;
- while (rNode = nextRNode()) {
- applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode);
- }
- applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
- }
- else if (tNodeType & 16 /* TNodeType.Projection */) {
- applyProjectionRecursive(renderer, action, lView, tNode, parentRElement, beforeNode);
- }
- else {
- ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */);
- applyToElementOrContainer(action, renderer, parentRElement, rawSlotValue, beforeNode);
- }
- }
- tNode = isProjection ? tNode.projectionNext : tNode.next;
- }
- }
- function applyView(tView, lView, renderer, action, parentRElement, beforeNode) {
- applyNodes(renderer, action, tView.firstChild, lView, parentRElement, beforeNode, false);
- }
- /**
- * `applyProjection` performs operation on the projection.
- *
- * Inserting a projection requires us to locate the projected nodes from the parent component. The
- * complication is that those nodes themselves could be re-projected from their parent component.
- *
- * @param tView The `TView` of `LView` which needs to be inserted, detached, destroyed
- * @param lView The `LView` which needs to be inserted, detached, destroyed.
- * @param tProjectionNode node to project
- */
- function applyProjection(tView, lView, tProjectionNode) {
- const renderer = lView[RENDERER];
- const parentRNode = getParentRElement(tView, tProjectionNode, lView);
- const parentTNode = tProjectionNode.parent || lView[T_HOST];
- let beforeNode = getInsertInFrontOfRNode(parentTNode, tProjectionNode, lView);
- applyProjectionRecursive(renderer, 0 /* WalkTNodeTreeAction.Create */, lView, tProjectionNode, parentRNode, beforeNode);
- }
- /**
- * `applyProjectionRecursive` performs operation on the projection specified by `action` (insert,
- * detach, destroy)
- *
- * Inserting a projection requires us to locate the projected nodes from the parent component. The
- * complication is that those nodes themselves could be re-projected from their parent component.
- *
- * @param renderer Render to use
- * @param action action to perform (insert, detach, destroy)
- * @param lView The LView which needs to be inserted, detached, destroyed.
- * @param tProjectionNode node to project
- * @param parentRElement parent DOM element for insertion/removal.
- * @param beforeNode Before which node the insertions should happen.
- */
- function applyProjectionRecursive(renderer, action, lView, tProjectionNode, parentRElement, beforeNode) {
- const componentLView = lView[DECLARATION_COMPONENT_VIEW];
- const componentNode = componentLView[T_HOST];
- ngDevMode &&
- assertEqual(typeof tProjectionNode.projection, 'number', 'expecting projection index');
- const nodeToProjectOrRNodes = componentNode.projection[tProjectionNode.projection];
- if (Array.isArray(nodeToProjectOrRNodes)) {
- // This should not exist, it is a bit of a hack. When we bootstrap a top level node and we
- // need to support passing projectable nodes, so we cheat and put them in the TNode
- // of the Host TView. (Yes we put instance info at the T Level). We can get away with it
- // because we know that that TView is not shared and therefore it will not be a problem.
- // This should be refactored and cleaned up.
- for (let i = 0; i < nodeToProjectOrRNodes.length; i++) {
- const rNode = nodeToProjectOrRNodes[i];
- applyToElementOrContainer(action, renderer, parentRElement, rNode, beforeNode);
- }
- }
- else {
- let nodeToProject = nodeToProjectOrRNodes;
- const projectedComponentLView = componentLView[PARENT];
- // If a parent <ng-content> is located within a skip hydration block,
- // annotate an actual node that is being projected with the same flag too.
- if (hasInSkipHydrationBlockFlag(tProjectionNode)) {
- nodeToProject.flags |= 128 /* TNodeFlags.inSkipHydrationBlock */;
- }
- applyNodes(renderer, action, nodeToProject, projectedComponentLView, parentRElement, beforeNode, true);
- }
- }
- /**
- * `applyContainer` performs an operation on the container and its views as specified by
- * `action` (insert, detach, destroy)
- *
- * Inserting a Container is complicated by the fact that the container may have Views which
- * themselves have containers or projections.
- *
- * @param renderer Renderer to use
- * @param action action to perform (insert, detach, destroy)
- * @param lContainer The LContainer which needs to be inserted, detached, destroyed.
- * @param parentRElement parent DOM element for insertion/removal.
- * @param beforeNode Before which node the insertions should happen.
- */
- function applyContainer(renderer, action, lContainer, parentRElement, beforeNode) {
- ngDevMode && assertLContainer(lContainer);
- const anchor = lContainer[NATIVE]; // LContainer has its own before node.
- const native = unwrapRNode(lContainer);
- // An LContainer can be created dynamically on any node by injecting ViewContainerRef.
- // Asking for a ViewContainerRef on an element will result in a creation of a separate anchor
- // node (comment in the DOM) that will be different from the LContainer's host node. In this
- // particular case we need to execute action on 2 nodes:
- // - container's host node (this is done in the executeActionOnElementOrContainer)
- // - container's host node (this is done here)
- if (anchor !== native) {
- // This is very strange to me (Misko). I would expect that the native is same as anchor. I
- // don't see a reason why they should be different, but they are.
- //
- // If they are we need to process the second anchor as well.
- applyToElementOrContainer(action, renderer, parentRElement, anchor, beforeNode);
- }
- for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
- const lView = lContainer[i];
- applyView(lView[TVIEW], lView, renderer, action, parentRElement, anchor);
- }
- }
- /**
- * Writes class/style to element.
- *
- * @param renderer Renderer to use.
- * @param isClassBased `true` if it should be written to `class` (`false` to write to `style`)
- * @param rNode The Node to write to.
- * @param prop Property to write to. This would be the class/style name.
- * @param value Value to write. If `null`/`undefined`/`false` this is considered a remove (set/add
- * otherwise).
- */
- function applyStyling(renderer, isClassBased, rNode, prop, value) {
- if (isClassBased) {
- // We actually want JS true/false here because any truthy value should add the class
- if (!value) {
- ngDevMode && ngDevMode.rendererRemoveClass++;
- renderer.removeClass(rNode, prop);
- }
- else {
- ngDevMode && ngDevMode.rendererAddClass++;
- renderer.addClass(rNode, prop);
- }
- }
- else {
- let flags = prop.indexOf('-') === -1 ? undefined : RendererStyleFlags2.DashCase;
- if (value == null /** || value === undefined */) {
- ngDevMode && ngDevMode.rendererRemoveStyle++;
- renderer.removeStyle(rNode, prop, flags);
- }
- else {
- // A value is important if it ends with `!important`. The style
- // parser strips any semicolons at the end of the value.
- const isImportant = typeof value === 'string' ? value.endsWith('!important') : false;
- if (isImportant) {
- // !important has to be stripped from the value for it to be valid.
- value = value.slice(0, -10);
- flags |= RendererStyleFlags2.Important;
- }
- ngDevMode && ngDevMode.rendererSetStyle++;
- renderer.setStyle(rNode, prop, value, flags);
- }
- }
- }
- /**
- * Write `cssText` to `RElement`.
- *
- * This function does direct write without any reconciliation. Used for writing initial values, so
- * that static styling values do not pull in the style parser.
- *
- * @param renderer Renderer to use
- * @param element The element which needs to be updated.
- * @param newValue The new class list to write.
- */
- function writeDirectStyle(renderer, element, newValue) {
- ngDevMode && assertString(newValue, '\'newValue\' should be a string');
- renderer.setAttribute(element, 'style', newValue);
- ngDevMode && ngDevMode.rendererSetStyle++;
- }
- /**
- * Write `className` to `RElement`.
- *
- * This function does direct write without any reconciliation. Used for writing initial values, so
- * that static styling values do not pull in the style parser.
- *
- * @param renderer Renderer to use
- * @param element The element which needs to be updated.
- * @param newValue The new class list to write.
- */
- function writeDirectClass(renderer, element, newValue) {
- ngDevMode && assertString(newValue, '\'newValue\' should be a string');
- if (newValue === '') {
- // There are tests in `google3` which expect `element.getAttribute('class')` to be `null`.
- renderer.removeAttribute(element, 'class');
- }
- else {
- renderer.setAttribute(element, 'class', newValue);
- }
- ngDevMode && ngDevMode.rendererSetClassName++;
- }
- /** Sets up the static DOM attributes on an `RNode`. */
- function setupStaticAttributes(renderer, element, tNode) {
- const { mergedAttrs, classes, styles } = tNode;
- if (mergedAttrs !== null) {
- setUpAttributes(renderer, element, mergedAttrs);
- }
- if (classes !== null) {
- writeDirectClass(renderer, element, classes);
- }
- if (styles !== null) {
- writeDirectStyle(renderer, element, styles);
- }
- }
- /**
- * @fileoverview
- * A module to facilitate use of a Trusted Types policy internally within
- * Angular. It lazily constructs the Trusted Types policy, providing helper
- * utilities for promoting strings to Trusted Types. When Trusted Types are not
- * available, strings are used as a fallback.
- * @security All use of this module is security-sensitive and should go through
- * security review.
- */
- /**
- * The Trusted Types policy, or null if Trusted Types are not
- * enabled/supported, or undefined if the policy has not been created yet.
- */
- let policy$1;
- /**
- * Returns the Trusted Types policy, or null if Trusted Types are not
- * enabled/supported. The first call to this function will create the policy.
- */
- function getPolicy$1() {
- if (policy$1 === undefined) {
- policy$1 = null;
- if (_global.trustedTypes) {
- try {
- policy$1 = _global.trustedTypes.createPolicy('angular', {
- createHTML: (s) => s,
- createScript: (s) => s,
- createScriptURL: (s) => s,
- });
- }
- catch {
- // trustedTypes.createPolicy throws if called with a name that is
- // already registered, even in report-only mode. Until the API changes,
- // catch the error not to break the applications functionally. In such
- // cases, the code will fall back to using strings.
- }
- }
- }
- return policy$1;
- }
- /**
- * Unsafely promote a string to a TrustedHTML, falling back to strings when
- * Trusted Types are not available.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that the
- * provided string will never cause an XSS vulnerability if used in a context
- * that will be interpreted as HTML by a browser, e.g. when assigning to
- * element.innerHTML.
- */
- function trustedHTMLFromString(html) {
- return getPolicy$1()?.createHTML(html) || html;
- }
- /**
- * Unsafely promote a string to a TrustedScript, falling back to strings when
- * Trusted Types are not available.
- * @security In particular, it must be assured that the provided string will
- * never cause an XSS vulnerability if used in a context that will be
- * interpreted and executed as a script by a browser, e.g. when calling eval.
- */
- function trustedScriptFromString(script) {
- return getPolicy$1()?.createScript(script) || script;
- }
- /**
- * Unsafely promote a string to a TrustedScriptURL, falling back to strings
- * when Trusted Types are not available.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that the
- * provided string will never cause an XSS vulnerability if used in a context
- * that will cause a browser to load and execute a resource, e.g. when
- * assigning to script.src.
- */
- function trustedScriptURLFromString(url) {
- return getPolicy$1()?.createScriptURL(url) || url;
- }
- /**
- * Unsafely call the Function constructor with the given string arguments. It
- * is only available in development mode, and should be stripped out of
- * production code.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that it
- * is only called from development code, as use in production code can lead to
- * XSS vulnerabilities.
- */
- function newTrustedFunctionForDev(...args) {
- if (typeof ngDevMode === 'undefined') {
- throw new Error('newTrustedFunctionForDev should never be called in production');
- }
- if (!_global.trustedTypes) {
- // In environments that don't support Trusted Types, fall back to the most
- // straightforward implementation:
- return new Function(...args);
- }
- // Chrome currently does not support passing TrustedScript to the Function
- // constructor. The following implements the workaround proposed on the page
- // below, where the Chromium bug is also referenced:
- // https://github.com/w3c/webappsec-trusted-types/wiki/Trusted-Types-for-function-constructor
- const fnArgs = args.slice(0, -1).join(',');
- const fnBody = args[args.length - 1];
- const body = `(function anonymous(${fnArgs}
- ) { ${fnBody}
- })`;
- // Using eval directly confuses the compiler and prevents this module from
- // being stripped out of JS binaries even if not used. The global['eval']
- // indirection fixes that.
- const fn = _global['eval'](trustedScriptFromString(body));
- if (fn.bind === undefined) {
- // Workaround for a browser bug that only exists in Chrome 83, where passing
- // a TrustedScript to eval just returns the TrustedScript back without
- // evaluating it. In that case, fall back to the most straightforward
- // implementation:
- return new Function(...args);
- }
- // To completely mimic the behavior of calling "new Function", two more
- // things need to happen:
- // 1. Stringifying the resulting function should return its source code
- fn.toString = () => body;
- // 2. When calling the resulting function, `this` should refer to `global`
- return fn.bind(_global);
- // When Trusted Types support in Function constructors is widely available,
- // the implementation of this function can be simplified to:
- // return new Function(...args.map(a => trustedScriptFromString(a)));
- }
- /**
- * Validation function invoked at runtime for each binding that might potentially
- * represent a security-sensitive attribute of an <iframe>.
- * See `IFRAME_SECURITY_SENSITIVE_ATTRS` in the
- * `packages/compiler/src/schema/dom_security_schema.ts` script for the full list
- * of such attributes.
- *
- * @codeGenApi
- */
- function ɵɵvalidateIframeAttribute(attrValue, tagName, attrName) {
- const lView = getLView();
- const tNode = getSelectedTNode();
- const element = getNativeByTNode(tNode, lView);
- // Restrict any dynamic bindings of security-sensitive attributes/properties
- // on an <iframe> for security reasons.
- if (tNode.type === 2 /* TNodeType.Element */ && tagName.toLowerCase() === 'iframe') {
- const iframe = element;
- // Unset previously applied `src` and `srcdoc` if we come across a situation when
- // a security-sensitive attribute is set later via an attribute/property binding.
- iframe.src = '';
- iframe.srcdoc = trustedHTMLFromString('');
- // Also remove the <iframe> from the document.
- nativeRemoveNode(lView[RENDERER], iframe);
- const errorMessage = ngDevMode &&
- `Angular has detected that the \`${attrName}\` was applied ` +
- `as a binding to an <iframe>${getTemplateLocationDetails(lView)}. ` +
- `For security reasons, the \`${attrName}\` can be set on an <iframe> ` +
- `as a static attribute only. \n` +
- `To fix this, switch the \`${attrName}\` binding to a static attribute ` +
- `in a template or in host bindings section.`;
- throw new RuntimeError(-910 /* RuntimeErrorCode.UNSAFE_IFRAME_ATTRS */, errorMessage);
- }
- return attrValue;
- }
- /**
- * @fileoverview
- * A module to facilitate use of a Trusted Types policy internally within
- * Angular specifically for bypassSecurityTrust* and custom sanitizers. It
- * lazily constructs the Trusted Types policy, providing helper utilities for
- * promoting strings to Trusted Types. When Trusted Types are not available,
- * strings are used as a fallback.
- * @security All use of this module is security-sensitive and should go through
- * security review.
- */
- /**
- * The Trusted Types policy, or null if Trusted Types are not
- * enabled/supported, or undefined if the policy has not been created yet.
- */
- let policy;
- /**
- * Returns the Trusted Types policy, or null if Trusted Types are not
- * enabled/supported. The first call to this function will create the policy.
- */
- function getPolicy() {
- if (policy === undefined) {
- policy = null;
- if (_global.trustedTypes) {
- try {
- policy = _global.trustedTypes
- .createPolicy('angular#unsafe-bypass', {
- createHTML: (s) => s,
- createScript: (s) => s,
- createScriptURL: (s) => s,
- });
- }
- catch {
- // trustedTypes.createPolicy throws if called with a name that is
- // already registered, even in report-only mode. Until the API changes,
- // catch the error not to break the applications functionally. In such
- // cases, the code will fall back to using strings.
- }
- }
- }
- return policy;
- }
- /**
- * Unsafely promote a string to a TrustedHTML, falling back to strings when
- * Trusted Types are not available.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that it
- * is only passed strings that come directly from custom sanitizers or the
- * bypassSecurityTrust* functions.
- */
- function trustedHTMLFromStringBypass(html) {
- return getPolicy()?.createHTML(html) || html;
- }
- /**
- * Unsafely promote a string to a TrustedScript, falling back to strings when
- * Trusted Types are not available.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that it
- * is only passed strings that come directly from custom sanitizers or the
- * bypassSecurityTrust* functions.
- */
- function trustedScriptFromStringBypass(script) {
- return getPolicy()?.createScript(script) || script;
- }
- /**
- * Unsafely promote a string to a TrustedScriptURL, falling back to strings
- * when Trusted Types are not available.
- * @security This is a security-sensitive function; any use of this function
- * must go through security review. In particular, it must be assured that it
- * is only passed strings that come directly from custom sanitizers or the
- * bypassSecurityTrust* functions.
- */
- function trustedScriptURLFromStringBypass(url) {
- return getPolicy()?.createScriptURL(url) || url;
- }
- class SafeValueImpl {
- constructor(changingThisBreaksApplicationSecurity) {
- this.changingThisBreaksApplicationSecurity = changingThisBreaksApplicationSecurity;
- }
- toString() {
- return `SafeValue must use [property]=binding: ${this.changingThisBreaksApplicationSecurity}` +
- ` (see ${XSS_SECURITY_URL})`;
- }
- }
- class SafeHtmlImpl extends SafeValueImpl {
- getTypeName() {
- return "HTML" /* BypassType.Html */;
- }
- }
- class SafeStyleImpl extends SafeValueImpl {
- getTypeName() {
- return "Style" /* BypassType.Style */;
- }
- }
- class SafeScriptImpl extends SafeValueImpl {
- getTypeName() {
- return "Script" /* BypassType.Script */;
- }
- }
- class SafeUrlImpl extends SafeValueImpl {
- getTypeName() {
- return "URL" /* BypassType.Url */;
- }
- }
- class SafeResourceUrlImpl extends SafeValueImpl {
- getTypeName() {
- return "ResourceURL" /* BypassType.ResourceUrl */;
- }
- }
- function unwrapSafeValue(value) {
- return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity :
- value;
- }
- function allowSanitizationBypassAndThrow(value, type) {
- const actualType = getSanitizationBypassType(value);
- if (actualType != null && actualType !== type) {
- // Allow ResourceURLs in URL contexts, they are strictly more trusted.
- if (actualType === "ResourceURL" /* BypassType.ResourceUrl */ && type === "URL" /* BypassType.Url */)
- return true;
- throw new Error(`Required a safe ${type}, got a ${actualType} (see ${XSS_SECURITY_URL})`);
- }
- return actualType === type;
- }
- function getSanitizationBypassType(value) {
- return value instanceof SafeValueImpl && value.getTypeName() || null;
- }
- /**
- * Mark `html` string as trusted.
- *
- * This function wraps the trusted string in `String` and brands it in a way which makes it
- * recognizable to {@link htmlSanitizer} to be trusted implicitly.
- *
- * @param trustedHtml `html` string which needs to be implicitly trusted.
- * @returns a `html` which has been branded to be implicitly trusted.
- */
- function bypassSanitizationTrustHtml(trustedHtml) {
- return new SafeHtmlImpl(trustedHtml);
- }
- /**
- * Mark `style` string as trusted.
- *
- * This function wraps the trusted string in `String` and brands it in a way which makes it
- * recognizable to {@link styleSanitizer} to be trusted implicitly.
- *
- * @param trustedStyle `style` string which needs to be implicitly trusted.
- * @returns a `style` hich has been branded to be implicitly trusted.
- */
- function bypassSanitizationTrustStyle(trustedStyle) {
- return new SafeStyleImpl(trustedStyle);
- }
- /**
- * Mark `script` string as trusted.
- *
- * This function wraps the trusted string in `String` and brands it in a way which makes it
- * recognizable to {@link scriptSanitizer} to be trusted implicitly.
- *
- * @param trustedScript `script` string which needs to be implicitly trusted.
- * @returns a `script` which has been branded to be implicitly trusted.
- */
- function bypassSanitizationTrustScript(trustedScript) {
- return new SafeScriptImpl(trustedScript);
- }
- /**
- * Mark `url` string as trusted.
- *
- * This function wraps the trusted string in `String` and brands it in a way which makes it
- * recognizable to {@link urlSanitizer} to be trusted implicitly.
- *
- * @param trustedUrl `url` string which needs to be implicitly trusted.
- * @returns a `url` which has been branded to be implicitly trusted.
- */
- function bypassSanitizationTrustUrl(trustedUrl) {
- return new SafeUrlImpl(trustedUrl);
- }
- /**
- * Mark `url` string as trusted.
- *
- * This function wraps the trusted string in `String` and brands it in a way which makes it
- * recognizable to {@link resourceUrlSanitizer} to be trusted implicitly.
- *
- * @param trustedResourceUrl `url` string which needs to be implicitly trusted.
- * @returns a `url` which has been branded to be implicitly trusted.
- */
- function bypassSanitizationTrustResourceUrl(trustedResourceUrl) {
- return new SafeResourceUrlImpl(trustedResourceUrl);
- }
- /**
- * This helper is used to get hold of an inert tree of DOM elements containing dirty HTML
- * that needs sanitizing.
- * Depending upon browser support we use one of two strategies for doing this.
- * Default: DOMParser strategy
- * Fallback: InertDocument strategy
- */
- function getInertBodyHelper(defaultDoc) {
- const inertDocumentHelper = new InertDocumentHelper(defaultDoc);
- return isDOMParserAvailable() ? new DOMParserHelper(inertDocumentHelper) : inertDocumentHelper;
- }
- /**
- * Uses DOMParser to create and fill an inert body element.
- * This is the default strategy used in browsers that support it.
- */
- class DOMParserHelper {
- constructor(inertDocumentHelper) {
- this.inertDocumentHelper = inertDocumentHelper;
- }
- getInertBodyElement(html) {
- // We add these extra elements to ensure that the rest of the content is parsed as expected
- // e.g. leading whitespace is maintained and tags like `<meta>` do not get hoisted to the
- // `<head>` tag. Note that the `<body>` tag is closed implicitly to prevent unclosed tags
- // in `html` from consuming the otherwise explicit `</body>` tag.
- html = '<body><remove></remove>' + html;
- try {
- const body = new window.DOMParser()
- .parseFromString(trustedHTMLFromString(html), 'text/html')
- .body;
- if (body === null) {
- // In some browsers (e.g. Mozilla/5.0 iPad AppleWebKit Mobile) the `body` property only
- // becomes available in the following tick of the JS engine. In that case we fall back to
- // the `inertDocumentHelper` instead.
- return this.inertDocumentHelper.getInertBodyElement(html);
- }
- body.removeChild(body.firstChild);
- return body;
- }
- catch {
- return null;
- }
- }
- }
- /**
- * Use an HTML5 `template` element to create and fill an inert DOM element.
- * This is the fallback strategy if the browser does not support DOMParser.
- */
- class InertDocumentHelper {
- constructor(defaultDoc) {
- this.defaultDoc = defaultDoc;
- this.inertDocument = this.defaultDoc.implementation.createHTMLDocument('sanitization-inert');
- }
- getInertBodyElement(html) {
- const templateEl = this.inertDocument.createElement('template');
- templateEl.innerHTML = trustedHTMLFromString(html);
- return templateEl;
- }
- }
- /**
- * We need to determine whether the DOMParser exists in the global context and
- * supports parsing HTML; HTML parsing support is not as wide as other formats, see
- * https://developer.mozilla.org/en-US/docs/Web/API/DOMParser#Browser_compatibility.
- *
- * @suppress {uselessCode}
- */
- function isDOMParserAvailable() {
- try {
- return !!new window.DOMParser().parseFromString(trustedHTMLFromString(''), 'text/html');
- }
- catch {
- return false;
- }
- }
- /**
- * A pattern that recognizes URLs that are safe wrt. XSS in URL navigation
- * contexts.
- *
- * This regular expression matches a subset of URLs that will not cause script
- * execution if used in URL context within a HTML document. Specifically, this
- * regular expression matches if:
- * (1) Either a protocol that is not javascript:, and that has valid characters
- * (alphanumeric or [+-.]).
- * (2) or no protocol. A protocol must be followed by a colon. The below
- * allows that by allowing colons only after one of the characters [/?#].
- * A colon after a hash (#) must be in the fragment.
- * Otherwise, a colon after a (?) must be in a query.
- * Otherwise, a colon after a single solidus (/) must be in a path.
- * Otherwise, a colon after a double solidus (//) must be in the authority
- * (before port).
- *
- * The pattern disallows &, used in HTML entity declarations before
- * one of the characters in [/?#]. This disallows HTML entities used in the
- * protocol name, which should never happen, e.g. "http" for "http".
- * It also disallows HTML entities in the first path part of a relative path,
- * e.g. "foo<bar/baz". Our existing escaping functions should not produce
- * that. More importantly, it disallows masking of a colon,
- * e.g. "javascript:...".
- *
- * This regular expression was taken from the Closure sanitization library.
- */
- const SAFE_URL_PATTERN = /^(?!javascript:)(?:[a-z0-9+.-]+:|[^&:\/?#]*(?:[\/?#]|$))/i;
- function _sanitizeUrl(url) {
- url = String(url);
- if (url.match(SAFE_URL_PATTERN))
- return url;
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- console.warn(`WARNING: sanitizing unsafe URL value ${url} (see ${XSS_SECURITY_URL})`);
- }
- return 'unsafe:' + url;
- }
- function tagSet(tags) {
- const res = {};
- for (const t of tags.split(','))
- res[t] = true;
- return res;
- }
- function merge(...sets) {
- const res = {};
- for (const s of sets) {
- for (const v in s) {
- if (s.hasOwnProperty(v))
- res[v] = true;
- }
- }
- return res;
- }
- // Good source of info about elements and attributes
- // https://html.spec.whatwg.org/#semantics
- // https://simon.html5.org/html-elements
- // Safe Void Elements - HTML5
- // https://html.spec.whatwg.org/#void-elements
- const VOID_ELEMENTS = tagSet('area,br,col,hr,img,wbr');
- // Elements that you can, intentionally, leave open (and which close themselves)
- // https://html.spec.whatwg.org/#optional-tags
- const OPTIONAL_END_TAG_BLOCK_ELEMENTS = tagSet('colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr');
- const OPTIONAL_END_TAG_INLINE_ELEMENTS = tagSet('rp,rt');
- const OPTIONAL_END_TAG_ELEMENTS = merge(OPTIONAL_END_TAG_INLINE_ELEMENTS, OPTIONAL_END_TAG_BLOCK_ELEMENTS);
- // Safe Block Elements - HTML5
- const BLOCK_ELEMENTS = merge(OPTIONAL_END_TAG_BLOCK_ELEMENTS, tagSet('address,article,' +
- 'aside,blockquote,caption,center,del,details,dialog,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,' +
- 'h6,header,hgroup,hr,ins,main,map,menu,nav,ol,pre,section,summary,table,ul'));
- // Inline Elements - HTML5
- const INLINE_ELEMENTS = merge(OPTIONAL_END_TAG_INLINE_ELEMENTS, tagSet('a,abbr,acronym,audio,b,' +
- 'bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,picture,q,ruby,rp,rt,s,' +
- 'samp,small,source,span,strike,strong,sub,sup,time,track,tt,u,var,video'));
- const VALID_ELEMENTS = merge(VOID_ELEMENTS, BLOCK_ELEMENTS, INLINE_ELEMENTS, OPTIONAL_END_TAG_ELEMENTS);
- // Attributes that have href and hence need to be sanitized
- const URI_ATTRS = tagSet('background,cite,href,itemtype,longdesc,poster,src,xlink:href');
- const HTML_ATTRS = tagSet('abbr,accesskey,align,alt,autoplay,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,' +
- 'compact,controls,coords,datetime,default,dir,download,face,headers,height,hidden,hreflang,hspace,' +
- 'ismap,itemscope,itemprop,kind,label,lang,language,loop,media,muted,nohref,nowrap,open,preload,rel,rev,role,rows,rowspan,rules,' +
- 'scope,scrolling,shape,size,sizes,span,srclang,srcset,start,summary,tabindex,target,title,translate,type,usemap,' +
- 'valign,value,vspace,width');
- // Accessibility attributes as per WAI-ARIA 1.1 (W3C Working Draft 14 December 2018)
- const ARIA_ATTRS = tagSet('aria-activedescendant,aria-atomic,aria-autocomplete,aria-busy,aria-checked,aria-colcount,aria-colindex,' +
- 'aria-colspan,aria-controls,aria-current,aria-describedby,aria-details,aria-disabled,aria-dropeffect,' +
- 'aria-errormessage,aria-expanded,aria-flowto,aria-grabbed,aria-haspopup,aria-hidden,aria-invalid,' +
- 'aria-keyshortcuts,aria-label,aria-labelledby,aria-level,aria-live,aria-modal,aria-multiline,' +
- 'aria-multiselectable,aria-orientation,aria-owns,aria-placeholder,aria-posinset,aria-pressed,aria-readonly,' +
- 'aria-relevant,aria-required,aria-roledescription,aria-rowcount,aria-rowindex,aria-rowspan,aria-selected,' +
- 'aria-setsize,aria-sort,aria-valuemax,aria-valuemin,aria-valuenow,aria-valuetext');
- // NB: This currently consciously doesn't support SVG. SVG sanitization has had several security
- // issues in the past, so it seems safer to leave it out if possible. If support for binding SVG via
- // innerHTML is required, SVG attributes should be added here.
- // NB: Sanitization does not allow <form> elements or other active elements (<button> etc). Those
- // can be sanitized, but they increase security surface area without a legitimate use case, so they
- // are left out here.
- const VALID_ATTRS = merge(URI_ATTRS, HTML_ATTRS, ARIA_ATTRS);
- // Elements whose content should not be traversed/preserved, if the elements themselves are invalid.
- //
- // Typically, `<invalid>Some content</invalid>` would traverse (and in this case preserve)
- // `Some content`, but strip `invalid-element` opening/closing tags. For some elements, though, we
- // don't want to preserve the content, if the elements themselves are going to be removed.
- const SKIP_TRAVERSING_CONTENT_IF_INVALID_ELEMENTS = tagSet('script,style,template');
- /**
- * SanitizingHtmlSerializer serializes a DOM fragment, stripping out any unsafe elements and unsafe
- * attributes.
- */
- class SanitizingHtmlSerializer {
- constructor() {
- // Explicitly track if something was stripped, to avoid accidentally warning of sanitization just
- // because characters were re-encoded.
- this.sanitizedSomething = false;
- this.buf = [];
- }
- sanitizeChildren(el) {
- // This cannot use a TreeWalker, as it has to run on Angular's various DOM adapters.
- // However this code never accesses properties off of `document` before deleting its contents
- // again, so it shouldn't be vulnerable to DOM clobbering.
- let current = el.firstChild;
- let traverseContent = true;
- while (current) {
- if (current.nodeType === Node.ELEMENT_NODE) {
- traverseContent = this.startElement(current);
- }
- else if (current.nodeType === Node.TEXT_NODE) {
- this.chars(current.nodeValue);
- }
- else {
- // Strip non-element, non-text nodes.
- this.sanitizedSomething = true;
- }
- if (traverseContent && current.firstChild) {
- current = current.firstChild;
- continue;
- }
- while (current) {
- // Leaving the element. Walk up and to the right, closing tags as we go.
- if (current.nodeType === Node.ELEMENT_NODE) {
- this.endElement(current);
- }
- let next = this.checkClobberedElement(current, current.nextSibling);
- if (next) {
- current = next;
- break;
- }
- current = this.checkClobberedElement(current, current.parentNode);
- }
- }
- return this.buf.join('');
- }
- /**
- * Sanitizes an opening element tag (if valid) and returns whether the element's contents should
- * be traversed. Element content must always be traversed (even if the element itself is not
- * valid/safe), unless the element is one of `SKIP_TRAVERSING_CONTENT_IF_INVALID_ELEMENTS`.
- *
- * @param element The element to sanitize.
- * @return True if the element's contents should be traversed.
- */
- startElement(element) {
- const tagName = element.nodeName.toLowerCase();
- if (!VALID_ELEMENTS.hasOwnProperty(tagName)) {
- this.sanitizedSomething = true;
- return !SKIP_TRAVERSING_CONTENT_IF_INVALID_ELEMENTS.hasOwnProperty(tagName);
- }
- this.buf.push('<');
- this.buf.push(tagName);
- const elAttrs = element.attributes;
- for (let i = 0; i < elAttrs.length; i++) {
- const elAttr = elAttrs.item(i);
- const attrName = elAttr.name;
- const lower = attrName.toLowerCase();
- if (!VALID_ATTRS.hasOwnProperty(lower)) {
- this.sanitizedSomething = true;
- continue;
- }
- let value = elAttr.value;
- // TODO(martinprobst): Special case image URIs for data:image/...
- if (URI_ATTRS[lower])
- value = _sanitizeUrl(value);
- this.buf.push(' ', attrName, '="', encodeEntities(value), '"');
- }
- this.buf.push('>');
- return true;
- }
- endElement(current) {
- const tagName = current.nodeName.toLowerCase();
- if (VALID_ELEMENTS.hasOwnProperty(tagName) && !VOID_ELEMENTS.hasOwnProperty(tagName)) {
- this.buf.push('</');
- this.buf.push(tagName);
- this.buf.push('>');
- }
- }
- chars(chars) {
- this.buf.push(encodeEntities(chars));
- }
- checkClobberedElement(node, nextNode) {
- if (nextNode &&
- (node.compareDocumentPosition(nextNode) &
- Node.DOCUMENT_POSITION_CONTAINED_BY) === Node.DOCUMENT_POSITION_CONTAINED_BY) {
- throw new Error(`Failed to sanitize html because the element is clobbered: ${node.outerHTML}`);
- }
- return nextNode;
- }
- }
- // Regular Expressions for parsing tags and attributes
- const SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
- // ! to ~ is the ASCII range.
- const NON_ALPHANUMERIC_REGEXP = /([^\#-~ |!])/g;
- /**
- * Escapes all potentially dangerous characters, so that the
- * resulting string can be safely inserted into attribute or
- * element text.
- * @param value
- */
- function encodeEntities(value) {
- return value.replace(/&/g, '&')
- .replace(SURROGATE_PAIR_REGEXP, function (match) {
- const hi = match.charCodeAt(0);
- const low = match.charCodeAt(1);
- return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
- })
- .replace(NON_ALPHANUMERIC_REGEXP, function (match) {
- return '&#' + match.charCodeAt(0) + ';';
- })
- .replace(/</g, '<')
- .replace(/>/g, '>');
- }
- let inertBodyHelper;
- /**
- * Sanitizes the given unsafe, untrusted HTML fragment, and returns HTML text that is safe to add to
- * the DOM in a browser environment.
- */
- function _sanitizeHtml(defaultDoc, unsafeHtmlInput) {
- let inertBodyElement = null;
- try {
- inertBodyHelper = inertBodyHelper || getInertBodyHelper(defaultDoc);
- // Make sure unsafeHtml is actually a string (TypeScript types are not enforced at runtime).
- let unsafeHtml = unsafeHtmlInput ? String(unsafeHtmlInput) : '';
- inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
- // mXSS protection. Repeatedly parse the document to make sure it stabilizes, so that a browser
- // trying to auto-correct incorrect HTML cannot cause formerly inert HTML to become dangerous.
- let mXSSAttempts = 5;
- let parsedHtml = unsafeHtml;
- do {
- if (mXSSAttempts === 0) {
- throw new Error('Failed to sanitize html because the input is unstable');
- }
- mXSSAttempts--;
- unsafeHtml = parsedHtml;
- parsedHtml = inertBodyElement.innerHTML;
- inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeHtml);
- } while (unsafeHtml !== parsedHtml);
- const sanitizer = new SanitizingHtmlSerializer();
- const safeHtml = sanitizer.sanitizeChildren(getTemplateContent(inertBodyElement) || inertBodyElement);
- if ((typeof ngDevMode === 'undefined' || ngDevMode) && sanitizer.sanitizedSomething) {
- console.warn(`WARNING: sanitizing HTML stripped some content, see ${XSS_SECURITY_URL}`);
- }
- return trustedHTMLFromString(safeHtml);
- }
- finally {
- // In case anything goes wrong, clear out inertElement to reset the entire DOM structure.
- if (inertBodyElement) {
- const parent = getTemplateContent(inertBodyElement) || inertBodyElement;
- while (parent.firstChild) {
- parent.removeChild(parent.firstChild);
- }
- }
- }
- }
- function getTemplateContent(el) {
- return 'content' in el /** Microsoft/TypeScript#21517 */ && isTemplateElement(el) ?
- el.content :
- null;
- }
- function isTemplateElement(el) {
- return el.nodeType === Node.ELEMENT_NODE && el.nodeName === 'TEMPLATE';
- }
- /**
- * A SecurityContext marks a location that has dangerous security implications, e.g. a DOM property
- * like `innerHTML` that could cause Cross Site Scripting (XSS) security bugs when improperly
- * handled.
- *
- * See DomSanitizer for more details on security in Angular applications.
- *
- * @publicApi
- */
- var SecurityContext;
- (function (SecurityContext) {
- SecurityContext[SecurityContext["NONE"] = 0] = "NONE";
- SecurityContext[SecurityContext["HTML"] = 1] = "HTML";
- SecurityContext[SecurityContext["STYLE"] = 2] = "STYLE";
- SecurityContext[SecurityContext["SCRIPT"] = 3] = "SCRIPT";
- SecurityContext[SecurityContext["URL"] = 4] = "URL";
- SecurityContext[SecurityContext["RESOURCE_URL"] = 5] = "RESOURCE_URL";
- })(SecurityContext || (SecurityContext = {}));
- /**
- * An `html` sanitizer which converts untrusted `html` **string** into trusted string by removing
- * dangerous content.
- *
- * This method parses the `html` and locates potentially dangerous content (such as urls and
- * javascript) and removes it.
- *
- * It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustHtml}.
- *
- * @param unsafeHtml untrusted `html`, typically from the user.
- * @returns `html` string which is safe to display to user, because all of the dangerous javascript
- * and urls have been removed.
- *
- * @codeGenApi
- */
- function ɵɵsanitizeHtml(unsafeHtml) {
- const sanitizer = getSanitizer();
- if (sanitizer) {
- return trustedHTMLFromStringBypass(sanitizer.sanitize(SecurityContext.HTML, unsafeHtml) || '');
- }
- if (allowSanitizationBypassAndThrow(unsafeHtml, "HTML" /* BypassType.Html */)) {
- return trustedHTMLFromStringBypass(unwrapSafeValue(unsafeHtml));
- }
- return _sanitizeHtml(getDocument(), renderStringify(unsafeHtml));
- }
- /**
- * A `style` sanitizer which converts untrusted `style` **string** into trusted string by removing
- * dangerous content.
- *
- * It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustStyle}.
- *
- * @param unsafeStyle untrusted `style`, typically from the user.
- * @returns `style` string which is safe to bind to the `style` properties.
- *
- * @codeGenApi
- */
- function ɵɵsanitizeStyle(unsafeStyle) {
- const sanitizer = getSanitizer();
- if (sanitizer) {
- return sanitizer.sanitize(SecurityContext.STYLE, unsafeStyle) || '';
- }
- if (allowSanitizationBypassAndThrow(unsafeStyle, "Style" /* BypassType.Style */)) {
- return unwrapSafeValue(unsafeStyle);
- }
- return renderStringify(unsafeStyle);
- }
- /**
- * A `url` sanitizer which converts untrusted `url` **string** into trusted string by removing
- * dangerous
- * content.
- *
- * This method parses the `url` and locates potentially dangerous content (such as javascript) and
- * removes it.
- *
- * It is possible to mark a string as trusted by calling {@link bypassSanitizationTrustUrl}.
- *
- * @param unsafeUrl untrusted `url`, typically from the user.
- * @returns `url` string which is safe to bind to the `src` properties such as `<img src>`, because
- * all of the dangerous javascript has been removed.
- *
- * @codeGenApi
- */
- function ɵɵsanitizeUrl(unsafeUrl) {
- const sanitizer = getSanitizer();
- if (sanitizer) {
- return sanitizer.sanitize(SecurityContext.URL, unsafeUrl) || '';
- }
- if (allowSanitizationBypassAndThrow(unsafeUrl, "URL" /* BypassType.Url */)) {
- return unwrapSafeValue(unsafeUrl);
- }
- return _sanitizeUrl(renderStringify(unsafeUrl));
- }
- /**
- * A `url` sanitizer which only lets trusted `url`s through.
- *
- * This passes only `url`s marked trusted by calling {@link bypassSanitizationTrustResourceUrl}.
- *
- * @param unsafeResourceUrl untrusted `url`, typically from the user.
- * @returns `url` string which is safe to bind to the `src` properties such as `<img src>`, because
- * only trusted `url`s have been allowed to pass.
- *
- * @codeGenApi
- */
- function ɵɵsanitizeResourceUrl(unsafeResourceUrl) {
- const sanitizer = getSanitizer();
- if (sanitizer) {
- return trustedScriptURLFromStringBypass(sanitizer.sanitize(SecurityContext.RESOURCE_URL, unsafeResourceUrl) || '');
- }
- if (allowSanitizationBypassAndThrow(unsafeResourceUrl, "ResourceURL" /* BypassType.ResourceUrl */)) {
- return trustedScriptURLFromStringBypass(unwrapSafeValue(unsafeResourceUrl));
- }
- throw new RuntimeError(904 /* RuntimeErrorCode.UNSAFE_VALUE_IN_RESOURCE_URL */, ngDevMode && `unsafe value used in a resource URL context (see ${XSS_SECURITY_URL})`);
- }
- /**
- * A `script` sanitizer which only lets trusted javascript through.
- *
- * This passes only `script`s marked trusted by calling {@link
- * bypassSanitizationTrustScript}.
- *
- * @param unsafeScript untrusted `script`, typically from the user.
- * @returns `url` string which is safe to bind to the `<script>` element such as `<img src>`,
- * because only trusted `scripts` have been allowed to pass.
- *
- * @codeGenApi
- */
- function ɵɵsanitizeScript(unsafeScript) {
- const sanitizer = getSanitizer();
- if (sanitizer) {
- return trustedScriptFromStringBypass(sanitizer.sanitize(SecurityContext.SCRIPT, unsafeScript) || '');
- }
- if (allowSanitizationBypassAndThrow(unsafeScript, "Script" /* BypassType.Script */)) {
- return trustedScriptFromStringBypass(unwrapSafeValue(unsafeScript));
- }
- throw new RuntimeError(905 /* RuntimeErrorCode.UNSAFE_VALUE_IN_SCRIPT */, ngDevMode && 'unsafe value used in a script context');
- }
- /**
- * A template tag function for promoting the associated constant literal to a
- * TrustedHTML. Interpolation is explicitly not allowed.
- *
- * @param html constant template literal containing trusted HTML.
- * @returns TrustedHTML wrapping `html`.
- *
- * @security This is a security-sensitive function and should only be used to
- * convert constant values of attributes and properties found in
- * application-provided Angular templates to TrustedHTML.
- *
- * @codeGenApi
- */
- function ɵɵtrustConstantHtml(html) {
- // The following runtime check ensures that the function was called as a
- // template tag (e.g. ɵɵtrustConstantHtml`content`), without any interpolation
- // (e.g. not ɵɵtrustConstantHtml`content ${variable}`). A TemplateStringsArray
- // is an array with a `raw` property that is also an array. The associated
- // template literal has no interpolation if and only if the length of the
- // TemplateStringsArray is 1.
- if (ngDevMode && (!Array.isArray(html) || !Array.isArray(html.raw) || html.length !== 1)) {
- throw new Error(`Unexpected interpolation in trusted HTML constant: ${html.join('?')}`);
- }
- return trustedHTMLFromString(html[0]);
- }
- /**
- * A template tag function for promoting the associated constant literal to a
- * TrustedScriptURL. Interpolation is explicitly not allowed.
- *
- * @param url constant template literal containing a trusted script URL.
- * @returns TrustedScriptURL wrapping `url`.
- *
- * @security This is a security-sensitive function and should only be used to
- * convert constant values of attributes and properties found in
- * application-provided Angular templates to TrustedScriptURL.
- *
- * @codeGenApi
- */
- function ɵɵtrustConstantResourceUrl(url) {
- // The following runtime check ensures that the function was called as a
- // template tag (e.g. ɵɵtrustConstantResourceUrl`content`), without any
- // interpolation (e.g. not ɵɵtrustConstantResourceUrl`content ${variable}`). A
- // TemplateStringsArray is an array with a `raw` property that is also an
- // array. The associated template literal has no interpolation if and only if
- // the length of the TemplateStringsArray is 1.
- if (ngDevMode && (!Array.isArray(url) || !Array.isArray(url.raw) || url.length !== 1)) {
- throw new Error(`Unexpected interpolation in trusted URL constant: ${url.join('?')}`);
- }
- return trustedScriptURLFromString(url[0]);
- }
- /**
- * Detects which sanitizer to use for URL property, based on tag name and prop name.
- *
- * The rules are based on the RESOURCE_URL context config from
- * `packages/compiler/src/schema/dom_security_schema.ts`.
- * If tag and prop names don't match Resource URL schema, use URL sanitizer.
- */
- function getUrlSanitizer(tag, prop) {
- if ((prop === 'src' &&
- (tag === 'embed' || tag === 'frame' || tag === 'iframe' || tag === 'media' ||
- tag === 'script')) ||
- (prop === 'href' && (tag === 'base' || tag === 'link'))) {
- return ɵɵsanitizeResourceUrl;
- }
- return ɵɵsanitizeUrl;
- }
- /**
- * Sanitizes URL, selecting sanitizer function based on tag and property names.
- *
- * This function is used in case we can't define security context at compile time, when only prop
- * name is available. This happens when we generate host bindings for Directives/Components. The
- * host element is unknown at compile time, so we defer calculation of specific sanitizer to
- * runtime.
- *
- * @param unsafeUrl untrusted `url`, typically from the user.
- * @param tag target element tag name.
- * @param prop name of the property that contains the value.
- * @returns `url` string which is safe to bind.
- *
- * @codeGenApi
- */
- function ɵɵsanitizeUrlOrResourceUrl(unsafeUrl, tag, prop) {
- return getUrlSanitizer(tag, prop)(unsafeUrl);
- }
- function validateAgainstEventProperties(name) {
- if (name.toLowerCase().startsWith('on')) {
- const errorMessage = `Binding to event property '${name}' is disallowed for security reasons, ` +
- `please use (${name.slice(2)})=...` +
- `\nIf '${name}' is a directive input, make sure the directive is imported by the` +
- ` current module.`;
- throw new RuntimeError(306 /* RuntimeErrorCode.INVALID_EVENT_BINDING */, errorMessage);
- }
- }
- function validateAgainstEventAttributes(name) {
- if (name.toLowerCase().startsWith('on')) {
- const errorMessage = `Binding to event attribute '${name}' is disallowed for security reasons, ` +
- `please use (${name.slice(2)})=...`;
- throw new RuntimeError(306 /* RuntimeErrorCode.INVALID_EVENT_BINDING */, errorMessage);
- }
- }
- function getSanitizer() {
- const lView = getLView();
- return lView && lView[ENVIRONMENT].sanitizer;
- }
- /**
- * Create a `StateKey<T>` that can be used to store value of type T with `TransferState`.
- *
- * Example:
- *
- * ```
- * const COUNTER_KEY = makeStateKey<number>('counter');
- * let value = 10;
- *
- * transferState.set(COUNTER_KEY, value);
- * ```
- *
- * @publicApi
- */
- function makeStateKey(key) {
- return key;
- }
- function initTransferState() {
- const transferState = new TransferState();
- if (inject$1(PLATFORM_ID) === 'browser') {
- transferState.store = retrieveTransferredState(getDocument(), inject$1(APP_ID));
- }
- return transferState;
- }
- /**
- * A key value store that is transferred from the application on the server side to the application
- * on the client side.
- *
- * The `TransferState` is available as an injectable token.
- * On the client, just inject this token using DI and use it, it will be lazily initialized.
- * On the server it's already included if `renderApplication` function is used. Otherwise, import
- * the `ServerTransferStateModule` module to make the `TransferState` available.
- *
- * The values in the store are serialized/deserialized using JSON.stringify/JSON.parse. So only
- * boolean, number, string, null and non-class objects will be serialized and deserialized in a
- * non-lossy manner.
- *
- * @publicApi
- */
- class TransferState {
- constructor() {
- /** @internal */
- this.store = {};
- this.onSerializeCallbacks = {};
- }
- /** @nocollapse */
- static { this.ɵprov =
- /** @pureOrBreakMyCode */ ɵɵdefineInjectable({
- token: TransferState,
- providedIn: 'root',
- factory: initTransferState,
- }); }
- /**
- * Get the value corresponding to a key. Return `defaultValue` if key is not found.
- */
- get(key, defaultValue) {
- return this.store[key] !== undefined ? this.store[key] : defaultValue;
- }
- /**
- * Set the value corresponding to a key.
- */
- set(key, value) {
- this.store[key] = value;
- }
- /**
- * Remove a key from the store.
- */
- remove(key) {
- delete this.store[key];
- }
- /**
- * Test whether a key exists in the store.
- */
- hasKey(key) {
- return this.store.hasOwnProperty(key);
- }
- /**
- * Indicates whether the state is empty.
- */
- get isEmpty() {
- return Object.keys(this.store).length === 0;
- }
- /**
- * Register a callback to provide the value for a key when `toJson` is called.
- */
- onSerialize(key, callback) {
- this.onSerializeCallbacks[key] = callback;
- }
- /**
- * Serialize the current state of the store to JSON.
- */
- toJson() {
- // Call the onSerialize callbacks and put those values into the store.
- for (const key in this.onSerializeCallbacks) {
- if (this.onSerializeCallbacks.hasOwnProperty(key)) {
- try {
- this.store[key] = this.onSerializeCallbacks[key]();
- }
- catch (e) {
- console.warn('Exception in onSerialize callback: ', e);
- }
- }
- }
- // Escape script tag to avoid break out of <script> tag in serialized output.
- // Encoding of `<` is the same behaviour as G3 script_builders.
- return JSON.stringify(this.store).replace(/</g, '\\u003C');
- }
- }
- function retrieveTransferredState(doc, appId) {
- // Locate the script tag with the JSON data transferred from the server.
- // The id of the script tag is set to the Angular appId + 'state'.
- const script = doc.getElementById(appId + '-state');
- if (script?.textContent) {
- try {
- // Avoid using any here as it triggers lint errors in google3 (any is not allowed).
- // Decoding of `<` is done of the box by browsers and node.js, same behaviour as G3
- // script_builders.
- return JSON.parse(script.textContent);
- }
- catch (e) {
- console.warn('Exception while restoring TransferState for app ' + appId, e);
- }
- }
- return {};
- }
- /** Encodes that the node lookup should start from the host node of this component. */
- const REFERENCE_NODE_HOST = 'h';
- /** Encodes that the node lookup should start from the document body node. */
- const REFERENCE_NODE_BODY = 'b';
- /**
- * Describes navigation steps that the runtime logic need to perform,
- * starting from a given (known) element.
- */
- var NodeNavigationStep;
- (function (NodeNavigationStep) {
- NodeNavigationStep["FirstChild"] = "f";
- NodeNavigationStep["NextSibling"] = "n";
- })(NodeNavigationStep || (NodeNavigationStep = {}));
- /**
- * Keys within serialized view data structure to represent various
- * parts. See the `SerializedView` interface below for additional information.
- */
- const ELEMENT_CONTAINERS = 'e';
- const TEMPLATES = 't';
- const CONTAINERS = 'c';
- const MULTIPLIER = 'x';
- const NUM_ROOT_NODES = 'r';
- const TEMPLATE_ID = 'i'; // as it's also an "id"
- const NODES = 'n';
- const DISCONNECTED_NODES = 'd';
- /**
- * The name of the key used in the TransferState collection,
- * where hydration information is located.
- */
- const TRANSFER_STATE_TOKEN_ID = '__ɵnghData__';
- /**
- * Lookup key used to reference DOM hydration data (ngh) in `TransferState`.
- */
- const NGH_DATA_KEY = makeStateKey(TRANSFER_STATE_TOKEN_ID);
- /**
- * The name of the attribute that would be added to host component
- * nodes and contain a reference to a particular slot in transferred
- * state that contains the necessary hydration info for this component.
- */
- const NGH_ATTR_NAME = 'ngh';
- /**
- * Marker used in a comment node to ensure hydration content integrity
- */
- const SSR_CONTENT_INTEGRITY_MARKER = 'nghm';
- /**
- * Reference to a function that reads `ngh` attribute value from a given RNode
- * and retrieves hydration information from the TransferState using that value
- * as an index. Returns `null` by default, when hydration is not enabled.
- *
- * @param rNode Component's host element.
- * @param injector Injector that this component has access to.
- * @param isRootView Specifies whether we trying to read hydration info for the root view.
- */
- let _retrieveHydrationInfoImpl = (rNode, injector, isRootView) => null;
- function retrieveHydrationInfoImpl(rNode, injector, isRootView = false) {
- let nghAttrValue = rNode.getAttribute(NGH_ATTR_NAME);
- if (nghAttrValue == null)
- return null;
- // For cases when a root component also acts as an anchor node for a ViewContainerRef
- // (for example, when ViewContainerRef is injected in a root component), there is a need
- // to serialize information about the component itself, as well as an LContainer that
- // represents this ViewContainerRef. Effectively, we need to serialize 2 pieces of info:
- // (1) hydration info for the root component itself and (2) hydration info for the
- // ViewContainerRef instance (an LContainer). Each piece of information is included into
- // the hydration data (in the TransferState object) separately, thus we end up with 2 ids.
- // Since we only have 1 root element, we encode both bits of info into a single string:
- // ids are separated by the `|` char (e.g. `10|25`, where `10` is the ngh for a component view
- // and 25 is the `ngh` for a root view which holds LContainer).
- const [componentViewNgh, rootViewNgh] = nghAttrValue.split('|');
- nghAttrValue = isRootView ? rootViewNgh : componentViewNgh;
- if (!nghAttrValue)
- return null;
- // We've read one of the ngh ids, keep the remaining one, so that
- // we can set it back on the DOM element.
- const remainingNgh = isRootView ? componentViewNgh : (rootViewNgh ? `|${rootViewNgh}` : '');
- let data = {};
- // An element might have an empty `ngh` attribute value (e.g. `<comp ngh="" />`),
- // which means that no special annotations are required. Do not attempt to read
- // from the TransferState in this case.
- if (nghAttrValue !== '') {
- const transferState = injector.get(TransferState, null, { optional: true });
- if (transferState !== null) {
- const nghData = transferState.get(NGH_DATA_KEY, []);
- // The nghAttrValue is always a number referencing an index
- // in the hydration TransferState data.
- data = nghData[Number(nghAttrValue)];
- // If the `ngh` attribute exists and has a non-empty value,
- // the hydration info *must* be present in the TransferState.
- // If there is no data for some reasons, this is an error.
- ngDevMode && assertDefined(data, 'Unable to retrieve hydration info from the TransferState.');
- }
- }
- const dehydratedView = {
- data,
- firstChild: rNode.firstChild ?? null,
- };
- if (isRootView) {
- // If there is hydration info present for the root view, it means that there was
- // a ViewContainerRef injected in the root component. The root component host element
- // acted as an anchor node in this scenario. As a result, the DOM nodes that represent
- // embedded views in this ViewContainerRef are located as siblings to the host node,
- // i.e. `<app-root /><#VIEW1><#VIEW2>...<!--container-->`. In this case, the current
- // node becomes the first child of this root view and the next sibling is the first
- // element in the DOM segment.
- dehydratedView.firstChild = rNode;
- // We use `0` here, since this is the slot (right after the HEADER_OFFSET)
- // where a component LView or an LContainer is located in a root LView.
- setSegmentHead(dehydratedView, 0, rNode.nextSibling);
- }
- if (remainingNgh) {
- // If we have only used one of the ngh ids, store the remaining one
- // back on this RNode.
- rNode.setAttribute(NGH_ATTR_NAME, remainingNgh);
- }
- else {
- // The `ngh` attribute is cleared from the DOM node now
- // that the data has been retrieved for all indices.
- rNode.removeAttribute(NGH_ATTR_NAME);
- }
- // Note: don't check whether this node was claimed for hydration,
- // because this node might've been previously claimed while processing
- // template instructions.
- ngDevMode && markRNodeAsClaimedByHydration(rNode, /* checkIfAlreadyClaimed */ false);
- ngDevMode && ngDevMode.hydratedComponents++;
- return dehydratedView;
- }
- /**
- * Sets the implementation for the `retrieveHydrationInfo` function.
- */
- function enableRetrieveHydrationInfoImpl() {
- _retrieveHydrationInfoImpl = retrieveHydrationInfoImpl;
- }
- /**
- * Retrieves hydration info by reading the value from the `ngh` attribute
- * and accessing a corresponding slot in TransferState storage.
- */
- function retrieveHydrationInfo(rNode, injector, isRootView = false) {
- return _retrieveHydrationInfoImpl(rNode, injector, isRootView);
- }
- /**
- * Retrieves the necessary object from a given ViewRef to serialize:
- * - an LView for component views
- * - an LContainer for cases when component acts as a ViewContainerRef anchor
- * - `null` in case of an embedded view
- */
- function getLNodeForHydration(viewRef) {
- // Reading an internal field from `ViewRef` instance.
- let lView = viewRef._lView;
- const tView = lView[TVIEW];
- // A registered ViewRef might represent an instance of an
- // embedded view, in which case we do not need to annotate it.
- if (tView.type === 2 /* TViewType.Embedded */) {
- return null;
- }
- // Check if it's a root view and if so, retrieve component's
- // LView from the first slot after the header.
- if (isRootView(lView)) {
- lView = lView[HEADER_OFFSET];
- }
- return lView;
- }
- function getTextNodeContent(node) {
- return node.textContent?.replace(/\s/gm, '');
- }
- /**
- * Restores text nodes and separators into the DOM that were lost during SSR
- * serialization. The hydration process replaces empty text nodes and text
- * nodes that are immediately adjacent to other text nodes with comment nodes
- * that this method filters on to restore those missing nodes that the
- * hydration process is expecting to be present.
- *
- * @param node The app's root HTML Element
- */
- function processTextNodeMarkersBeforeHydration(node) {
- const doc = getDocument();
- const commentNodesIterator = doc.createNodeIterator(node, NodeFilter.SHOW_COMMENT, {
- acceptNode(node) {
- const content = getTextNodeContent(node);
- const isTextNodeMarker = content === "ngetn" /* TextNodeMarker.EmptyNode */ || content === "ngtns" /* TextNodeMarker.Separator */;
- return isTextNodeMarker ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
- }
- });
- let currentNode;
- // We cannot modify the DOM while using the commentIterator,
- // because it throws off the iterator state.
- // So we collect all marker nodes first and then follow up with
- // applying the changes to the DOM: either inserting an empty node
- // or just removing the marker if it was used as a separator.
- const nodes = [];
- while (currentNode = commentNodesIterator.nextNode()) {
- nodes.push(currentNode);
- }
- for (const node of nodes) {
- if (node.textContent === "ngetn" /* TextNodeMarker.EmptyNode */) {
- node.replaceWith(doc.createTextNode(''));
- }
- else {
- node.remove();
- }
- }
- }
- /**
- * Marks a node as "claimed" by hydration process.
- * This is needed to make assessments in tests whether
- * the hydration process handled all nodes.
- */
- function markRNodeAsClaimedByHydration(node, checkIfAlreadyClaimed = true) {
- if (!ngDevMode) {
- throw new Error('Calling `markRNodeAsClaimedByHydration` in prod mode ' +
- 'is not supported and likely a mistake.');
- }
- if (checkIfAlreadyClaimed && isRNodeClaimedForHydration(node)) {
- throw new Error('Trying to claim a node, which was claimed already.');
- }
- node.__claimed = true;
- ngDevMode.hydratedNodes++;
- }
- function isRNodeClaimedForHydration(node) {
- return !!node.__claimed;
- }
- function setSegmentHead(hydrationInfo, index, node) {
- hydrationInfo.segmentHeads ??= {};
- hydrationInfo.segmentHeads[index] = node;
- }
- function getSegmentHead(hydrationInfo, index) {
- return hydrationInfo.segmentHeads?.[index] ?? null;
- }
- /**
- * Returns the size of an <ng-container>, using either the information
- * serialized in `ELEMENT_CONTAINERS` (element container size) or by
- * computing the sum of root nodes in all dehydrated views in a given
- * container (in case this `<ng-container>` was also used as a view
- * container host node, e.g. <ng-container *ngIf>).
- */
- function getNgContainerSize(hydrationInfo, index) {
- const data = hydrationInfo.data;
- let size = data[ELEMENT_CONTAINERS]?.[index] ?? null;
- // If there is no serialized information available in the `ELEMENT_CONTAINERS` slot,
- // check if we have info about view containers at this location (e.g.
- // `<ng-container *ngIf>`) and use container size as a number of root nodes in this
- // element container.
- if (size === null && data[CONTAINERS]?.[index]) {
- size = calcSerializedContainerSize(hydrationInfo, index);
- }
- return size;
- }
- function getSerializedContainerViews(hydrationInfo, index) {
- return hydrationInfo.data[CONTAINERS]?.[index] ?? null;
- }
- /**
- * Computes the size of a serialized container (the number of root nodes)
- * by calculating the sum of root nodes in all dehydrated views in this container.
- */
- function calcSerializedContainerSize(hydrationInfo, index) {
- const views = getSerializedContainerViews(hydrationInfo, index) ?? [];
- let numNodes = 0;
- for (let view of views) {
- numNodes += view[NUM_ROOT_NODES] * (view[MULTIPLIER] ?? 1);
- }
- return numNodes;
- }
- /**
- * Checks whether a node is annotated as "disconnected", i.e. not present
- * in the DOM at serialization time. We should not attempt hydration for
- * such nodes and instead, use a regular "creation mode".
- */
- function isDisconnectedNode(hydrationInfo, index) {
- // Check if we are processing disconnected info for the first time.
- if (typeof hydrationInfo.disconnectedNodes === 'undefined') {
- const nodeIds = hydrationInfo.data[DISCONNECTED_NODES];
- hydrationInfo.disconnectedNodes = nodeIds ? (new Set(nodeIds)) : null;
- }
- return !!hydrationInfo.disconnectedNodes?.has(index);
- }
- /**
- * Represents a component created by a `ComponentFactory`.
- * Provides access to the component instance and related objects,
- * and provides the means of destroying the instance.
- *
- * @publicApi
- */
- class ComponentRef$1 {
- }
- /**
- * Base class for a factory that can create a component dynamically.
- * Instantiate a factory for a given type of component with `resolveComponentFactory()`.
- * Use the resulting `ComponentFactory.create()` method to create a component of that type.
- *
- * @see [Dynamic Components](guide/dynamic-component-loader)
- *
- * @publicApi
- *
- * @deprecated Angular no longer requires Component factories. Please use other APIs where
- * Component class can be used directly.
- */
- class ComponentFactory$1 {
- }
- function noComponentFactoryError(component) {
- const error = Error(`No component factory found for ${stringify(component)}.`);
- error[ERROR_COMPONENT] = component;
- return error;
- }
- const ERROR_COMPONENT = 'ngComponent';
- function getComponent$1(error) {
- return error[ERROR_COMPONENT];
- }
- class _NullComponentFactoryResolver {
- resolveComponentFactory(component) {
- throw noComponentFactoryError(component);
- }
- }
- /**
- * A simple registry that maps `Components` to generated `ComponentFactory` classes
- * that can be used to create instances of components.
- * Use to obtain the factory for a given component type,
- * then use the factory's `create()` method to create a component of that type.
- *
- * Note: since v13, dynamic component creation via
- * [`ViewContainerRef.createComponent`](api/core/ViewContainerRef#createComponent)
- * does **not** require resolving component factory: component class can be used directly.
- *
- * @publicApi
- *
- * @deprecated Angular no longer requires Component factories. Please use other APIs where
- * Component class can be used directly.
- */
- class ComponentFactoryResolver$1 {
- static { this.NULL = ( /* @__PURE__ */new _NullComponentFactoryResolver()); }
- }
- /**
- * Creates an ElementRef from the most recent node.
- *
- * @returns The ElementRef instance to use
- */
- function injectElementRef() {
- return createElementRef(getCurrentTNode(), getLView());
- }
- /**
- * Creates an ElementRef given a node.
- *
- * @param tNode The node for which you'd like an ElementRef
- * @param lView The view to which the node belongs
- * @returns The ElementRef instance to use
- */
- function createElementRef(tNode, lView) {
- return new ElementRef(getNativeByTNode(tNode, lView));
- }
- /**
- * A wrapper around a native element inside of a View.
- *
- * An `ElementRef` is backed by a render-specific element. In the browser, this is usually a DOM
- * element.
- *
- * @security Permitting direct access to the DOM can make your application more vulnerable to
- * XSS attacks. Carefully review any use of `ElementRef` in your code. For more detail, see the
- * [Security Guide](https://g.co/ng/security).
- *
- * @publicApi
- */
- // Note: We don't expose things like `Injector`, `ViewContainer`, ... here,
- // i.e. users have to ask for what they need. With that, we can build better analysis tools
- // and could do better codegen in the future.
- class ElementRef {
- constructor(nativeElement) {
- this.nativeElement = nativeElement;
- }
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ELEMENT_ID__ = injectElementRef; }
- }
- /**
- * Unwraps `ElementRef` and return the `nativeElement`.
- *
- * @param value value to unwrap
- * @returns `nativeElement` if `ElementRef` otherwise returns value as is.
- */
- function unwrapElementRef(value) {
- return value instanceof ElementRef ? value.nativeElement : value;
- }
- /**
- * Creates and initializes a custom renderer that implements the `Renderer2` base class.
- *
- * @publicApi
- */
- class RendererFactory2 {
- }
- /**
- * Extend this base class to implement custom rendering. By default, Angular
- * renders a template into DOM. You can use custom rendering to intercept
- * rendering calls, or to render to something other than DOM.
- *
- * Create your custom renderer using `RendererFactory2`.
- *
- * Use a custom renderer to bypass Angular's templating and
- * make custom UI changes that can't be expressed declaratively.
- * For example if you need to set a property or an attribute whose name is
- * not statically known, use the `setProperty()` or
- * `setAttribute()` method.
- *
- * @publicApi
- */
- class Renderer2 {
- constructor() {
- /**
- * If null or undefined, the view engine won't call it.
- * This is used as a performance optimization for production mode.
- */
- this.destroyNode = null;
- }
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ELEMENT_ID__ = () => injectRenderer2(); }
- }
- /** Injects a Renderer2 for the current component. */
- function injectRenderer2() {
- // We need the Renderer to be based on the component that it's being injected into, however since
- // DI happens before we've entered its view, `getLView` will return the parent view instead.
- const lView = getLView();
- const tNode = getCurrentTNode();
- const nodeAtIndex = getComponentLViewByIndex(tNode.index, lView);
- return (isLView(nodeAtIndex) ? nodeAtIndex : lView)[RENDERER];
- }
- /**
- * Sanitizer is used by the views to sanitize potentially dangerous values.
- *
- * @publicApi
- */
- class Sanitizer {
- /** @nocollapse */
- static { this.ɵprov = ɵɵdefineInjectable({
- token: Sanitizer,
- providedIn: 'root',
- factory: () => null,
- }); }
- }
- /**
- * @description Represents the version of Angular
- *
- * @publicApi
- */
- class Version {
- constructor(full) {
- this.full = full;
- this.major = full.split('.')[0];
- this.minor = full.split('.')[1];
- this.patch = full.split('.').slice(2).join('.');
- }
- }
- /**
- * @publicApi
- */
- const VERSION = new Version('16.2.9');
- // This default value is when checking the hierarchy for a token.
- //
- // It means both:
- // - the token is not provided by the current injector,
- // - only the element injectors should be checked (ie do not check module injectors
- //
- // mod1
- // /
- // el1 mod2
- // \ /
- // el2
- //
- // When requesting el2.injector.get(token), we should check in the following order and return the
- // first found value:
- // - el2.injector.get(token, default)
- // - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
- // - mod2.injector.get(token, default)
- const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
- const ERROR_ORIGINAL_ERROR = 'ngOriginalError';
- function wrappedError(message, originalError) {
- const msg = `${message} caused by: ${originalError instanceof Error ? originalError.message : originalError}`;
- const error = Error(msg);
- error[ERROR_ORIGINAL_ERROR] = originalError;
- return error;
- }
- function getOriginalError(error) {
- return error[ERROR_ORIGINAL_ERROR];
- }
- /**
- * Provides a hook for centralized exception handling.
- *
- * The default implementation of `ErrorHandler` prints error messages to the `console`. To
- * intercept error handling, write a custom exception handler that replaces this default as
- * appropriate for your app.
- *
- * @usageNotes
- * ### Example
- *
- * ```
- * class MyErrorHandler implements ErrorHandler {
- * handleError(error) {
- * // do something with the exception
- * }
- * }
- *
- * @NgModule({
- * providers: [{provide: ErrorHandler, useClass: MyErrorHandler}]
- * })
- * class MyModule {}
- * ```
- *
- * @publicApi
- */
- class ErrorHandler {
- constructor() {
- /**
- * @internal
- */
- this._console = console;
- }
- handleError(error) {
- const originalError = this._findOriginalError(error);
- this._console.error('ERROR', error);
- if (originalError) {
- this._console.error('ORIGINAL ERROR', originalError);
- }
- }
- /** @internal */
- _findOriginalError(error) {
- let e = error && getOriginalError(error);
- while (e && getOriginalError(e)) {
- e = getOriginalError(e);
- }
- return e || null;
- }
- }
- /**
- * `DestroyRef` lets you set callbacks to run for any cleanup or destruction behavior.
- * The scope of this destruction depends on where `DestroyRef` is injected. If `DestroyRef`
- * is injected in a component or directive, the callbacks run when that component or
- * directive is destroyed. Otherwise the callbacks run when a corresponding injector is destroyed.
- *
- * @publicApi
- */
- class DestroyRef {
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ELEMENT_ID__ = injectDestroyRef; }
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ENV_ID__ = (injector) => injector; }
- }
- class NodeInjectorDestroyRef extends DestroyRef {
- constructor(_lView) {
- super();
- this._lView = _lView;
- }
- onDestroy(callback) {
- storeLViewOnDestroy(this._lView, callback);
- return () => removeLViewOnDestroy(this._lView, callback);
- }
- }
- function injectDestroyRef() {
- return new NodeInjectorDestroyRef(getLView());
- }
- /// <reference types="rxjs" />
- class EventEmitter_ extends Subject {
- constructor(isAsync = false) {
- super();
- this.__isAsync = isAsync;
- }
- emit(value) {
- super.next(value);
- }
- subscribe(observerOrNext, error, complete) {
- let nextFn = observerOrNext;
- let errorFn = error || (() => null);
- let completeFn = complete;
- if (observerOrNext && typeof observerOrNext === 'object') {
- const observer = observerOrNext;
- nextFn = observer.next?.bind(observer);
- errorFn = observer.error?.bind(observer);
- completeFn = observer.complete?.bind(observer);
- }
- if (this.__isAsync) {
- errorFn = _wrapInTimeout(errorFn);
- if (nextFn) {
- nextFn = _wrapInTimeout(nextFn);
- }
- if (completeFn) {
- completeFn = _wrapInTimeout(completeFn);
- }
- }
- const sink = super.subscribe({ next: nextFn, error: errorFn, complete: completeFn });
- if (observerOrNext instanceof Subscription) {
- observerOrNext.add(sink);
- }
- return sink;
- }
- }
- function _wrapInTimeout(fn) {
- return (value) => {
- setTimeout(fn, undefined, value);
- };
- }
- /**
- * @publicApi
- */
- const EventEmitter = EventEmitter_;
- function noop(...args) {
- // Do nothing.
- }
- function getNativeRequestAnimationFrame() {
- // Note: the `getNativeRequestAnimationFrame` is used in the `NgZone` class, but we cannot use the
- // `inject` function. The `NgZone` instance may be created manually, and thus the injection
- // context will be unavailable. This might be enough to check whether `requestAnimationFrame` is
- // available because otherwise, we'll fall back to `setTimeout`.
- const isBrowser = typeof _global['requestAnimationFrame'] === 'function';
- // Note: `requestAnimationFrame` is unavailable when the code runs in the Node.js environment. We
- // use `setTimeout` because no changes are required other than checking if the current platform is
- // the browser. `setTimeout` is a well-established API that is available in both environments.
- // `requestAnimationFrame` is used in the browser to coalesce event tasks since event tasks are
- // usually executed within the same rendering frame (but this is more implementation details of
- // browsers).
- let nativeRequestAnimationFrame = _global[isBrowser ? 'requestAnimationFrame' : 'setTimeout'];
- let nativeCancelAnimationFrame = _global[isBrowser ? 'cancelAnimationFrame' : 'clearTimeout'];
- if (typeof Zone !== 'undefined' && nativeRequestAnimationFrame && nativeCancelAnimationFrame) {
- // Note: zone.js sets original implementations on patched APIs behind the
- // `__zone_symbol__OriginalDelegate` key (see `attachOriginToPatched`). Given the following
- // example: `window.requestAnimationFrame.__zone_symbol__OriginalDelegate`; this would return an
- // unpatched implementation of the `requestAnimationFrame`, which isn't intercepted by the
- // Angular zone. We use the unpatched implementation to avoid another change detection when
- // coalescing tasks.
- const unpatchedRequestAnimationFrame = nativeRequestAnimationFrame[Zone.__symbol__('OriginalDelegate')];
- if (unpatchedRequestAnimationFrame) {
- nativeRequestAnimationFrame = unpatchedRequestAnimationFrame;
- }
- const unpatchedCancelAnimationFrame = nativeCancelAnimationFrame[Zone.__symbol__('OriginalDelegate')];
- if (unpatchedCancelAnimationFrame) {
- nativeCancelAnimationFrame = unpatchedCancelAnimationFrame;
- }
- }
- return { nativeRequestAnimationFrame, nativeCancelAnimationFrame };
- }
- class AsyncStackTaggingZoneSpec {
- constructor(namePrefix, consoleAsyncStackTaggingImpl = console) {
- this.name = 'asyncStackTagging for ' + namePrefix;
- this.createTask = consoleAsyncStackTaggingImpl?.createTask ?? (() => null);
- }
- onScheduleTask(delegate, _current, target, task) {
- task.consoleTask = this.createTask(`Zone - ${task.source || task.type}`);
- return delegate.scheduleTask(target, task);
- }
- onInvokeTask(delegate, _currentZone, targetZone, task, applyThis, applyArgs) {
- let ret;
- if (task.consoleTask) {
- ret = task.consoleTask.run(() => delegate.invokeTask(targetZone, task, applyThis, applyArgs));
- }
- else {
- ret = delegate.invokeTask(targetZone, task, applyThis, applyArgs);
- }
- return ret;
- }
- }
- /**
- * An injectable service for executing work inside or outside of the Angular zone.
- *
- * The most common use of this service is to optimize performance when starting a work consisting of
- * one or more asynchronous tasks that don't require UI updates or error handling to be handled by
- * Angular. Such tasks can be kicked off via {@link #runOutsideAngular} and if needed, these tasks
- * can reenter the Angular zone via {@link #run}.
- *
- * <!-- TODO: add/fix links to:
- * - docs explaining zones and the use of zones in Angular and change-detection
- * - link to runOutsideAngular/run (throughout this file!)
- * -->
- *
- * @usageNotes
- * ### Example
- *
- * ```
- * import {Component, NgZone} from '@angular/core';
- * import {NgIf} from '@angular/common';
- *
- * @Component({
- * selector: 'ng-zone-demo',
- * template: `
- * <h2>Demo: NgZone</h2>
- *
- * <p>Progress: {{progress}}%</p>
- * <p *ngIf="progress >= 100">Done processing {{label}} of Angular zone!</p>
- *
- * <button (click)="processWithinAngularZone()">Process within Angular zone</button>
- * <button (click)="processOutsideOfAngularZone()">Process outside of Angular zone</button>
- * `,
- * })
- * export class NgZoneDemo {
- * progress: number = 0;
- * label: string;
- *
- * constructor(private _ngZone: NgZone) {}
- *
- * // Loop inside the Angular zone
- * // so the UI DOES refresh after each setTimeout cycle
- * processWithinAngularZone() {
- * this.label = 'inside';
- * this.progress = 0;
- * this._increaseProgress(() => console.log('Inside Done!'));
- * }
- *
- * // Loop outside of the Angular zone
- * // so the UI DOES NOT refresh after each setTimeout cycle
- * processOutsideOfAngularZone() {
- * this.label = 'outside';
- * this.progress = 0;
- * this._ngZone.runOutsideAngular(() => {
- * this._increaseProgress(() => {
- * // reenter the Angular zone and display done
- * this._ngZone.run(() => { console.log('Outside Done!'); });
- * });
- * });
- * }
- *
- * _increaseProgress(doneCallback: () => void) {
- * this.progress += 1;
- * console.log(`Current progress: ${this.progress}%`);
- *
- * if (this.progress < 100) {
- * window.setTimeout(() => this._increaseProgress(doneCallback), 10);
- * } else {
- * doneCallback();
- * }
- * }
- * }
- * ```
- *
- * @publicApi
- */
- class NgZone {
- constructor({ enableLongStackTrace = false, shouldCoalesceEventChangeDetection = false, shouldCoalesceRunChangeDetection = false }) {
- this.hasPendingMacrotasks = false;
- this.hasPendingMicrotasks = false;
- /**
- * Whether there are no outstanding microtasks or macrotasks.
- */
- this.isStable = true;
- /**
- * Notifies when code enters Angular Zone. This gets fired first on VM Turn.
- */
- this.onUnstable = new EventEmitter(false);
- /**
- * Notifies when there is no more microtasks enqueued in the current VM Turn.
- * This is a hint for Angular to do change detection, which may enqueue more microtasks.
- * For this reason this event can fire multiple times per VM Turn.
- */
- this.onMicrotaskEmpty = new EventEmitter(false);
- /**
- * Notifies when the last `onMicrotaskEmpty` has run and there are no more microtasks, which
- * implies we are about to relinquish VM turn.
- * This event gets called just once.
- */
- this.onStable = new EventEmitter(false);
- /**
- * Notifies that an error has been delivered.
- */
- this.onError = new EventEmitter(false);
- if (typeof Zone == 'undefined') {
- throw new RuntimeError(908 /* RuntimeErrorCode.MISSING_ZONEJS */, ngDevMode && `In this configuration Angular requires Zone.js`);
- }
- Zone.assertZonePatched();
- const self = this;
- self._nesting = 0;
- self._outer = self._inner = Zone.current;
- // AsyncStackTaggingZoneSpec provides `linked stack traces` to show
- // where the async operation is scheduled. For more details, refer
- // to this article, https://developer.chrome.com/blog/devtools-better-angular-debugging/
- // And we only import this AsyncStackTaggingZoneSpec in development mode,
- // in the production mode, the AsyncStackTaggingZoneSpec will be tree shaken away.
- if (ngDevMode) {
- self._inner = self._inner.fork(new AsyncStackTaggingZoneSpec('Angular'));
- }
- if (Zone['TaskTrackingZoneSpec']) {
- self._inner = self._inner.fork(new Zone['TaskTrackingZoneSpec']);
- }
- if (enableLongStackTrace && Zone['longStackTraceZoneSpec']) {
- self._inner = self._inner.fork(Zone['longStackTraceZoneSpec']);
- }
- // if shouldCoalesceRunChangeDetection is true, all tasks including event tasks will be
- // coalesced, so shouldCoalesceEventChangeDetection option is not necessary and can be skipped.
- self.shouldCoalesceEventChangeDetection =
- !shouldCoalesceRunChangeDetection && shouldCoalesceEventChangeDetection;
- self.shouldCoalesceRunChangeDetection = shouldCoalesceRunChangeDetection;
- self.lastRequestAnimationFrameId = -1;
- self.nativeRequestAnimationFrame = getNativeRequestAnimationFrame().nativeRequestAnimationFrame;
- forkInnerZoneWithAngularBehavior(self);
- }
- /**
- This method checks whether the method call happens within an Angular Zone instance.
- */
- static isInAngularZone() {
- // Zone needs to be checked, because this method might be called even when NoopNgZone is used.
- return typeof Zone !== 'undefined' && Zone.current.get('isAngularZone') === true;
- }
- /**
- Assures that the method is called within the Angular Zone, otherwise throws an error.
- */
- static assertInAngularZone() {
- if (!NgZone.isInAngularZone()) {
- throw new RuntimeError(909 /* RuntimeErrorCode.UNEXPECTED_ZONE_STATE */, ngDevMode && 'Expected to be in Angular Zone, but it is not!');
- }
- }
- /**
- Assures that the method is called outside of the Angular Zone, otherwise throws an error.
- */
- static assertNotInAngularZone() {
- if (NgZone.isInAngularZone()) {
- throw new RuntimeError(909 /* RuntimeErrorCode.UNEXPECTED_ZONE_STATE */, ngDevMode && 'Expected to not be in Angular Zone, but it is!');
- }
- }
- /**
- * Executes the `fn` function synchronously within the Angular zone and returns value returned by
- * the function.
- *
- * Running functions via `run` allows you to reenter Angular zone from a task that was executed
- * outside of the Angular zone (typically started via {@link #runOutsideAngular}).
- *
- * Any future tasks or microtasks scheduled from within this function will continue executing from
- * within the Angular zone.
- *
- * If a synchronous error happens it will be rethrown and not reported via `onError`.
- */
- run(fn, applyThis, applyArgs) {
- return this._inner.run(fn, applyThis, applyArgs);
- }
- /**
- * Executes the `fn` function synchronously within the Angular zone as a task and returns value
- * returned by the function.
- *
- * Running functions via `run` allows you to reenter Angular zone from a task that was executed
- * outside of the Angular zone (typically started via {@link #runOutsideAngular}).
- *
- * Any future tasks or microtasks scheduled from within this function will continue executing from
- * within the Angular zone.
- *
- * If a synchronous error happens it will be rethrown and not reported via `onError`.
- */
- runTask(fn, applyThis, applyArgs, name) {
- const zone = this._inner;
- const task = zone.scheduleEventTask('NgZoneEvent: ' + name, fn, EMPTY_PAYLOAD, noop, noop);
- try {
- return zone.runTask(task, applyThis, applyArgs);
- }
- finally {
- zone.cancelTask(task);
- }
- }
- /**
- * Same as `run`, except that synchronous errors are caught and forwarded via `onError` and not
- * rethrown.
- */
- runGuarded(fn, applyThis, applyArgs) {
- return this._inner.runGuarded(fn, applyThis, applyArgs);
- }
- /**
- * Executes the `fn` function synchronously in Angular's parent zone and returns value returned by
- * the function.
- *
- * Running functions via {@link #runOutsideAngular} allows you to escape Angular's zone and do
- * work that
- * doesn't trigger Angular change-detection or is subject to Angular's error handling.
- *
- * Any future tasks or microtasks scheduled from within this function will continue executing from
- * outside of the Angular zone.
- *
- * Use {@link #run} to reenter the Angular zone and do work that updates the application model.
- */
- runOutsideAngular(fn) {
- return this._outer.run(fn);
- }
- }
- const EMPTY_PAYLOAD = {};
- function checkStable(zone) {
- // TODO: @JiaLiPassion, should check zone.isCheckStableRunning to prevent
- // re-entry. The case is:
- //
- // @Component({...})
- // export class AppComponent {
- // constructor(private ngZone: NgZone) {
- // this.ngZone.onStable.subscribe(() => {
- // this.ngZone.run(() => console.log('stable'););
- // });
- // }
- //
- // The onStable subscriber run another function inside ngZone
- // which causes `checkStable()` re-entry.
- // But this fix causes some issues in g3, so this fix will be
- // launched in another PR.
- if (zone._nesting == 0 && !zone.hasPendingMicrotasks && !zone.isStable) {
- try {
- zone._nesting++;
- zone.onMicrotaskEmpty.emit(null);
- }
- finally {
- zone._nesting--;
- if (!zone.hasPendingMicrotasks) {
- try {
- zone.runOutsideAngular(() => zone.onStable.emit(null));
- }
- finally {
- zone.isStable = true;
- }
- }
- }
- }
- }
- function delayChangeDetectionForEvents(zone) {
- /**
- * We also need to check _nesting here
- * Consider the following case with shouldCoalesceRunChangeDetection = true
- *
- * ngZone.run(() => {});
- * ngZone.run(() => {});
- *
- * We want the two `ngZone.run()` only trigger one change detection
- * when shouldCoalesceRunChangeDetection is true.
- * And because in this case, change detection run in async way(requestAnimationFrame),
- * so we also need to check the _nesting here to prevent multiple
- * change detections.
- */
- if (zone.isCheckStableRunning || zone.lastRequestAnimationFrameId !== -1) {
- return;
- }
- zone.lastRequestAnimationFrameId = zone.nativeRequestAnimationFrame.call(_global, () => {
- // This is a work around for https://github.com/angular/angular/issues/36839.
- // The core issue is that when event coalescing is enabled it is possible for microtasks
- // to get flushed too early (As is the case with `Promise.then`) between the
- // coalescing eventTasks.
- //
- // To workaround this we schedule a "fake" eventTask before we process the
- // coalescing eventTasks. The benefit of this is that the "fake" container eventTask
- // will prevent the microtasks queue from getting drained in between the coalescing
- // eventTask execution.
- if (!zone.fakeTopEventTask) {
- zone.fakeTopEventTask = Zone.root.scheduleEventTask('fakeTopEventTask', () => {
- zone.lastRequestAnimationFrameId = -1;
- updateMicroTaskStatus(zone);
- zone.isCheckStableRunning = true;
- checkStable(zone);
- zone.isCheckStableRunning = false;
- }, undefined, () => { }, () => { });
- }
- zone.fakeTopEventTask.invoke();
- });
- updateMicroTaskStatus(zone);
- }
- function forkInnerZoneWithAngularBehavior(zone) {
- const delayChangeDetectionForEventsDelegate = () => {
- delayChangeDetectionForEvents(zone);
- };
- zone._inner = zone._inner.fork({
- name: 'angular',
- properties: { 'isAngularZone': true },
- onInvokeTask: (delegate, current, target, task, applyThis, applyArgs) => {
- if (shouldBeIgnoredByZone(applyArgs)) {
- return delegate.invokeTask(target, task, applyThis, applyArgs);
- }
- try {
- onEnter(zone);
- return delegate.invokeTask(target, task, applyThis, applyArgs);
- }
- finally {
- if ((zone.shouldCoalesceEventChangeDetection && task.type === 'eventTask') ||
- zone.shouldCoalesceRunChangeDetection) {
- delayChangeDetectionForEventsDelegate();
- }
- onLeave(zone);
- }
- },
- onInvoke: (delegate, current, target, callback, applyThis, applyArgs, source) => {
- try {
- onEnter(zone);
- return delegate.invoke(target, callback, applyThis, applyArgs, source);
- }
- finally {
- if (zone.shouldCoalesceRunChangeDetection) {
- delayChangeDetectionForEventsDelegate();
- }
- onLeave(zone);
- }
- },
- onHasTask: (delegate, current, target, hasTaskState) => {
- delegate.hasTask(target, hasTaskState);
- if (current === target) {
- // We are only interested in hasTask events which originate from our zone
- // (A child hasTask event is not interesting to us)
- if (hasTaskState.change == 'microTask') {
- zone._hasPendingMicrotasks = hasTaskState.microTask;
- updateMicroTaskStatus(zone);
- checkStable(zone);
- }
- else if (hasTaskState.change == 'macroTask') {
- zone.hasPendingMacrotasks = hasTaskState.macroTask;
- }
- }
- },
- onHandleError: (delegate, current, target, error) => {
- delegate.handleError(target, error);
- zone.runOutsideAngular(() => zone.onError.emit(error));
- return false;
- }
- });
- }
- function updateMicroTaskStatus(zone) {
- if (zone._hasPendingMicrotasks ||
- ((zone.shouldCoalesceEventChangeDetection || zone.shouldCoalesceRunChangeDetection) &&
- zone.lastRequestAnimationFrameId !== -1)) {
- zone.hasPendingMicrotasks = true;
- }
- else {
- zone.hasPendingMicrotasks = false;
- }
- }
- function onEnter(zone) {
- zone._nesting++;
- if (zone.isStable) {
- zone.isStable = false;
- zone.onUnstable.emit(null);
- }
- }
- function onLeave(zone) {
- zone._nesting--;
- checkStable(zone);
- }
- /**
- * Provides a noop implementation of `NgZone` which does nothing. This zone requires explicit calls
- * to framework to perform rendering.
- */
- class NoopNgZone {
- constructor() {
- this.hasPendingMicrotasks = false;
- this.hasPendingMacrotasks = false;
- this.isStable = true;
- this.onUnstable = new EventEmitter();
- this.onMicrotaskEmpty = new EventEmitter();
- this.onStable = new EventEmitter();
- this.onError = new EventEmitter();
- }
- run(fn, applyThis, applyArgs) {
- return fn.apply(applyThis, applyArgs);
- }
- runGuarded(fn, applyThis, applyArgs) {
- return fn.apply(applyThis, applyArgs);
- }
- runOutsideAngular(fn) {
- return fn();
- }
- runTask(fn, applyThis, applyArgs, name) {
- return fn.apply(applyThis, applyArgs);
- }
- }
- /**
- * Token used to drive ApplicationRef.isStable
- *
- * TODO: This should be moved entirely to NgZone (as a breaking change) so it can be tree-shakeable
- * for `NoopNgZone` which is always just an `Observable` of `true`. Additionally, we should consider
- * whether the property on `NgZone` should be `Observable` or `Signal`.
- */
- const ZONE_IS_STABLE_OBSERVABLE = new InjectionToken(ngDevMode ? 'isStable Observable' : '', {
- providedIn: 'root',
- // TODO(atscott): Replace this with a suitable default like `new
- // BehaviorSubject(true).asObservable`. Again, long term this won't exist on ApplicationRef at
- // all but until we can remove it, we need a default value zoneless.
- factory: isStableFactory,
- });
- function isStableFactory() {
- const zone = inject$1(NgZone);
- let _stable = true;
- const isCurrentlyStable = new Observable((observer) => {
- _stable = zone.isStable && !zone.hasPendingMacrotasks && !zone.hasPendingMicrotasks;
- zone.runOutsideAngular(() => {
- observer.next(_stable);
- observer.complete();
- });
- });
- const isStable = new Observable((observer) => {
- // Create the subscription to onStable outside the Angular Zone so that
- // the callback is run outside the Angular Zone.
- let stableSub;
- zone.runOutsideAngular(() => {
- stableSub = zone.onStable.subscribe(() => {
- NgZone.assertNotInAngularZone();
- // Check whether there are no pending macro/micro tasks in the next tick
- // to allow for NgZone to update the state.
- queueMicrotask(() => {
- if (!_stable && !zone.hasPendingMacrotasks && !zone.hasPendingMicrotasks) {
- _stable = true;
- observer.next(true);
- }
- });
- });
- });
- const unstableSub = zone.onUnstable.subscribe(() => {
- NgZone.assertInAngularZone();
- if (_stable) {
- _stable = false;
- zone.runOutsideAngular(() => {
- observer.next(false);
- });
- }
- });
- return () => {
- stableSub.unsubscribe();
- unstableSub.unsubscribe();
- };
- });
- return merge$1(isCurrentlyStable, isStable.pipe(share()));
- }
- function shouldBeIgnoredByZone(applyArgs) {
- if (!Array.isArray(applyArgs)) {
- return false;
- }
- // We should only ever get 1 arg passed through to invokeTask.
- // Short circuit here incase that behavior changes.
- if (applyArgs.length !== 1) {
- return false;
- }
- // Prevent triggering change detection when the __ignore_ng_zone__ flag is detected.
- return applyArgs[0].data?.['__ignore_ng_zone__'] === true;
- }
- // Public API for Zone
- /**
- * Register a callback to be invoked each time the application
- * finishes rendering.
- *
- * Note that the callback will run
- * - in the order it was registered
- * - once per render
- * - on browser platforms only
- *
- * <div class="alert is-important">
- *
- * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.
- * You must use caution when directly reading or writing the DOM and layout.
- *
- * </div>
- *
- * @param callback A callback function to register
- *
- * @usageNotes
- *
- * Use `afterRender` to read or write the DOM after each render.
- *
- * ### Example
- * ```ts
- * @Component({
- * selector: 'my-cmp',
- * template: `<span #content>{{ ... }}</span>`,
- * })
- * export class MyComponent {
- * @ViewChild('content') contentRef: ElementRef;
- *
- * constructor() {
- * afterRender(() => {
- * console.log('content height: ' + this.contentRef.nativeElement.scrollHeight);
- * });
- * }
- * }
- * ```
- *
- * @developerPreview
- */
- function afterRender(callback, options) {
- !options && assertInInjectionContext(afterRender);
- const injector = options?.injector ?? inject$1(Injector);
- if (!isPlatformBrowser(injector)) {
- return { destroy() { } };
- }
- let destroy;
- const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
- const afterRenderEventManager = injector.get(AfterRenderEventManager);
- // Lazily initialize the handler implementation, if necessary. This is so that it can be
- // tree-shaken if `afterRender` and `afterNextRender` aren't used.
- const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
- const ngZone = injector.get(NgZone);
- const errorHandler = injector.get(ErrorHandler, null, { optional: true });
- const instance = new AfterRenderCallback(ngZone, errorHandler, callback);
- destroy = () => {
- callbackHandler.unregister(instance);
- unregisterFn();
- };
- callbackHandler.register(instance);
- return { destroy };
- }
- /**
- * Register a callback to be invoked the next time the application
- * finishes rendering.
- *
- * Note that the callback will run
- * - in the order it was registered
- * - on browser platforms only
- *
- * <div class="alert is-important">
- *
- * Components are not guaranteed to be [hydrated](guide/hydration) before the callback runs.
- * You must use caution when directly reading or writing the DOM and layout.
- *
- * </div>
- *
- * @param callback A callback function to register
- *
- * @usageNotes
- *
- * Use `afterNextRender` to read or write the DOM once,
- * for example to initialize a non-Angular library.
- *
- * ### Example
- * ```ts
- * @Component({
- * selector: 'my-chart-cmp',
- * template: `<div #chart>{{ ... }}</div>`,
- * })
- * export class MyChartCmp {
- * @ViewChild('chart') chartRef: ElementRef;
- * chart: MyChart|null;
- *
- * constructor() {
- * afterNextRender(() => {
- * this.chart = new MyChart(this.chartRef.nativeElement);
- * });
- * }
- * }
- * ```
- *
- * @developerPreview
- */
- function afterNextRender(callback, options) {
- !options && assertInInjectionContext(afterNextRender);
- const injector = options?.injector ?? inject$1(Injector);
- if (!isPlatformBrowser(injector)) {
- return { destroy() { } };
- }
- let destroy;
- const unregisterFn = injector.get(DestroyRef).onDestroy(() => destroy?.());
- const afterRenderEventManager = injector.get(AfterRenderEventManager);
- // Lazily initialize the handler implementation, if necessary. This is so that it can be
- // tree-shaken if `afterRender` and `afterNextRender` aren't used.
- const callbackHandler = afterRenderEventManager.handler ??= new AfterRenderCallbackHandlerImpl();
- const ngZone = injector.get(NgZone);
- const errorHandler = injector.get(ErrorHandler, null, { optional: true });
- const instance = new AfterRenderCallback(ngZone, errorHandler, () => {
- destroy?.();
- callback();
- });
- destroy = () => {
- callbackHandler.unregister(instance);
- unregisterFn();
- };
- callbackHandler.register(instance);
- return { destroy };
- }
- /**
- * A wrapper around a function to be used as an after render callback.
- */
- class AfterRenderCallback {
- constructor(zone, errorHandler, callbackFn) {
- this.zone = zone;
- this.errorHandler = errorHandler;
- this.callbackFn = callbackFn;
- }
- invoke() {
- try {
- this.zone.runOutsideAngular(this.callbackFn);
- }
- catch (err) {
- this.errorHandler?.handleError(err);
- }
- }
- }
- /**
- * Core functionality for `afterRender` and `afterNextRender`. Kept separate from
- * `AfterRenderEventManager` for tree-shaking.
- */
- class AfterRenderCallbackHandlerImpl {
- constructor() {
- this.executingCallbacks = false;
- this.callbacks = new Set();
- this.deferredCallbacks = new Set();
- }
- validateBegin() {
- if (this.executingCallbacks) {
- throw new RuntimeError(102 /* RuntimeErrorCode.RECURSIVE_APPLICATION_RENDER */, ngDevMode &&
- 'A new render operation began before the previous operation ended. ' +
- 'Did you trigger change detection from afterRender or afterNextRender?');
- }
- }
- register(callback) {
- // If we're currently running callbacks, new callbacks should be deferred
- // until the next render operation.
- const target = this.executingCallbacks ? this.deferredCallbacks : this.callbacks;
- target.add(callback);
- }
- unregister(callback) {
- this.callbacks.delete(callback);
- this.deferredCallbacks.delete(callback);
- }
- execute() {
- this.executingCallbacks = true;
- for (const callback of this.callbacks) {
- callback.invoke();
- }
- this.executingCallbacks = false;
- for (const callback of this.deferredCallbacks) {
- this.callbacks.add(callback);
- }
- this.deferredCallbacks.clear();
- }
- destroy() {
- this.callbacks.clear();
- this.deferredCallbacks.clear();
- }
- }
- /**
- * Implements core timing for `afterRender` and `afterNextRender` events.
- * Delegates to an optional `AfterRenderCallbackHandler` for implementation.
- */
- class AfterRenderEventManager {
- constructor() {
- this.renderDepth = 0;
- /* @internal */
- this.handler = null;
- }
- /**
- * Mark the beginning of a render operation (i.e. CD cycle).
- * Throws if called while executing callbacks.
- */
- begin() {
- this.handler?.validateBegin();
- this.renderDepth++;
- }
- /**
- * Mark the end of a render operation. Callbacks will be
- * executed if there are no more pending operations.
- */
- end() {
- ngDevMode && assertGreaterThan(this.renderDepth, 0, 'renderDepth must be greater than 0');
- this.renderDepth--;
- if (this.renderDepth === 0) {
- this.handler?.execute();
- }
- }
- ngOnDestroy() {
- this.handler?.destroy();
- this.handler = null;
- }
- /** @nocollapse */
- static { this.ɵprov = ɵɵdefineInjectable({
- token: AfterRenderEventManager,
- providedIn: 'root',
- factory: () => new AfterRenderEventManager(),
- }); }
- }
- /**
- * Marks current view and all ancestors dirty.
- *
- * Returns the root view because it is found as a byproduct of marking the view tree
- * dirty, and can be used by methods that consume markViewDirty() to easily schedule
- * change detection. Otherwise, such methods would need to traverse up the view tree
- * an additional time to get the root view and schedule a tick on it.
- *
- * @param lView The starting LView to mark dirty
- * @returns the root LView
- */
- function markViewDirty(lView) {
- while (lView) {
- lView[FLAGS] |= 64 /* LViewFlags.Dirty */;
- const parent = getLViewParent(lView);
- // Stop traversing up as soon as you find a root view that wasn't attached to any container
- if (isRootView(lView) && !parent) {
- return lView;
- }
- // continue otherwise
- lView = parent;
- }
- return null;
- }
- /**
- * Internal token that specifies whether DOM reuse logic
- * during hydration is enabled.
- */
- const IS_HYDRATION_DOM_REUSE_ENABLED = new InjectionToken((typeof ngDevMode === 'undefined' || !!ngDevMode) ? 'IS_HYDRATION_DOM_REUSE_ENABLED' : '');
- // By default (in client rendering mode), we remove all the contents
- // of the host element and render an application after that.
- const PRESERVE_HOST_CONTENT_DEFAULT = false;
- /**
- * Internal token that indicates whether host element content should be
- * retained during the bootstrap.
- */
- const PRESERVE_HOST_CONTENT = new InjectionToken((typeof ngDevMode === 'undefined' || !!ngDevMode) ? 'PRESERVE_HOST_CONTENT' : '', {
- providedIn: 'root',
- factory: () => PRESERVE_HOST_CONTENT_DEFAULT,
- });
- function normalizeDebugBindingName(name) {
- // Attribute names with `$` (eg `x-y$`) are valid per spec, but unsupported by some browsers
- name = camelCaseToDashCase(name.replace(/[$@]/g, '_'));
- return `ng-reflect-${name}`;
- }
- const CAMEL_CASE_REGEXP = /([A-Z])/g;
- function camelCaseToDashCase(input) {
- return input.replace(CAMEL_CASE_REGEXP, (...m) => '-' + m[1].toLowerCase());
- }
- function normalizeDebugBindingValue(value) {
- try {
- // Limit the size of the value as otherwise the DOM just gets polluted.
- return value != null ? value.toString().slice(0, 30) : value;
- }
- catch (e) {
- return '[ERROR] Exception while trying to serialize the value';
- }
- }
- /**
- * The max length of the string representation of a value in an error message
- */
- const VALUE_STRING_LENGTH_LIMIT = 200;
- /** Verifies that a given type is a Standalone Component. */
- function assertStandaloneComponentType(type) {
- assertComponentDef(type);
- const componentDef = getComponentDef$1(type);
- if (!componentDef.standalone) {
- throw new RuntimeError(907 /* RuntimeErrorCode.TYPE_IS_NOT_STANDALONE */, `The ${stringifyForError(type)} component is not marked as standalone, ` +
- `but Angular expects to have a standalone component here. ` +
- `Please make sure the ${stringifyForError(type)} component has ` +
- `the \`standalone: true\` flag in the decorator.`);
- }
- }
- /** Verifies whether a given type is a component */
- function assertComponentDef(type) {
- if (!getComponentDef$1(type)) {
- throw new RuntimeError(906 /* RuntimeErrorCode.MISSING_GENERATED_DEF */, `The ${stringifyForError(type)} is not an Angular component, ` +
- `make sure it has the \`@Component\` decorator.`);
- }
- }
- /** Called when there are multiple component selectors that match a given node */
- function throwMultipleComponentError(tNode, first, second) {
- throw new RuntimeError(-300 /* RuntimeErrorCode.MULTIPLE_COMPONENTS_MATCH */, `Multiple components match node with tagname ${tNode.value}: ` +
- `${stringifyForError(first)} and ` +
- `${stringifyForError(second)}`);
- }
- /** Throws an ExpressionChangedAfterChecked error if checkNoChanges mode is on. */
- function throwErrorIfNoChangesMode(creationMode, oldValue, currValue, propName, lView) {
- const hostComponentDef = getDeclarationComponentDef(lView);
- const componentClassName = hostComponentDef?.type?.name;
- const field = propName ? ` for '${propName}'` : '';
- let msg = `ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value${field}: '${formatValue(oldValue)}'. Current value: '${formatValue(currValue)}'.${componentClassName ? ` Expression location: ${componentClassName} component` : ''}`;
- if (creationMode) {
- msg +=
- ` It seems like the view has been created after its parent and its children have been dirty checked.` +
- ` Has it been created in a change detection hook?`;
- }
- throw new RuntimeError(-100 /* RuntimeErrorCode.EXPRESSION_CHANGED_AFTER_CHECKED */, msg);
- }
- function formatValue(value) {
- let strValue = String(value);
- // JSON.stringify will throw on circular references
- try {
- if (Array.isArray(value) || strValue === '[object Object]') {
- strValue = JSON.stringify(value);
- }
- }
- catch (error) {
- }
- return strValue.length > VALUE_STRING_LENGTH_LIMIT ?
- (strValue.substring(0, VALUE_STRING_LENGTH_LIMIT) + '…') :
- strValue;
- }
- function constructDetailsForInterpolation(lView, rootIndex, expressionIndex, meta, changedValue) {
- const [propName, prefix, ...chunks] = meta.split(INTERPOLATION_DELIMITER);
- let oldValue = prefix, newValue = prefix;
- for (let i = 0; i < chunks.length; i++) {
- const slotIdx = rootIndex + i;
- oldValue += `${lView[slotIdx]}${chunks[i]}`;
- newValue += `${slotIdx === expressionIndex ? changedValue : lView[slotIdx]}${chunks[i]}`;
- }
- return { propName, oldValue, newValue };
- }
- /**
- * Constructs an object that contains details for the ExpressionChangedAfterItHasBeenCheckedError:
- * - property name (for property bindings or interpolations)
- * - old and new values, enriched using information from metadata
- *
- * More information on the metadata storage format can be found in `storePropertyBindingMetadata`
- * function description.
- */
- function getExpressionChangedErrorDetails(lView, bindingIndex, oldValue, newValue) {
- const tData = lView[TVIEW].data;
- const metadata = tData[bindingIndex];
- if (typeof metadata === 'string') {
- // metadata for property interpolation
- if (metadata.indexOf(INTERPOLATION_DELIMITER) > -1) {
- return constructDetailsForInterpolation(lView, bindingIndex, bindingIndex, metadata, newValue);
- }
- // metadata for property binding
- return { propName: metadata, oldValue, newValue };
- }
- // metadata is not available for this expression, check if this expression is a part of the
- // property interpolation by going from the current binding index left and look for a string that
- // contains INTERPOLATION_DELIMITER, the layout in tView.data for this case will look like this:
- // [..., 'id�Prefix � and � suffix', null, null, null, ...]
- if (metadata === null) {
- let idx = bindingIndex - 1;
- while (typeof tData[idx] !== 'string' && tData[idx + 1] === null) {
- idx--;
- }
- const meta = tData[idx];
- if (typeof meta === 'string') {
- const matches = meta.match(new RegExp(INTERPOLATION_DELIMITER, 'g'));
- // first interpolation delimiter separates property name from interpolation parts (in case of
- // property interpolations), so we subtract one from total number of found delimiters
- if (matches && (matches.length - 1) > bindingIndex - idx) {
- return constructDetailsForInterpolation(lView, idx, bindingIndex, meta, newValue);
- }
- }
- }
- return { propName: undefined, oldValue, newValue };
- }
- let currentConsumer = null;
- function setLViewForConsumer(node, lView) {
- (typeof ngDevMode === 'undefined' || ngDevMode) &&
- assertEqual(node.lView, null, 'Consumer already associated with a view.');
- node.lView = lView;
- }
- /**
- * Create a new template consumer pointing at the specified LView.
- * Sometimes, a previously created consumer may be reused, in order to save on allocations. In that
- * case, the LView will be updated.
- */
- function getReactiveLViewConsumer(lView, slot) {
- return lView[slot] ?? getOrCreateCurrentLViewConsumer();
- }
- /**
- * Assigns the `currentTemplateContext` to its LView's `REACTIVE_CONSUMER` slot if there are tracked
- * producers.
- *
- * The presence of producers means that a signal was read while the consumer was the active
- * consumer.
- *
- * If no producers are present, we do not assign the current template context. This also means we
- * can just reuse the template context for the next LView.
- */
- function commitLViewConsumerIfHasProducers(lView, slot) {
- const consumer = getOrCreateCurrentLViewConsumer();
- if (!consumer.producerNode?.length) {
- return;
- }
- lView[slot] = currentConsumer;
- consumer.lView = lView;
- currentConsumer = createLViewConsumer();
- }
- const REACTIVE_LVIEW_CONSUMER_NODE = {
- ...REACTIVE_NODE,
- consumerIsAlwaysLive: true,
- consumerMarkedDirty: (node) => {
- (typeof ngDevMode === 'undefined' || ngDevMode) &&
- assertDefined(node.lView, 'Updating a signal during template or host binding execution is not allowed.');
- markViewDirty(node.lView);
- },
- lView: null,
- };
- function createLViewConsumer() {
- return Object.create(REACTIVE_LVIEW_CONSUMER_NODE);
- }
- function getOrCreateCurrentLViewConsumer() {
- currentConsumer ??= createLViewConsumer();
- return currentConsumer;
- }
- /** A special value which designates that a value has not changed. */
- const NO_CHANGE = (typeof ngDevMode === 'undefined' || ngDevMode) ? { __brand__: 'NO_CHANGE' } : {};
- /**
- * Advances to an element for later binding instructions.
- *
- * Used in conjunction with instructions like {@link property} to act on elements with specified
- * indices, for example those created with {@link element} or {@link elementStart}.
- *
- * ```ts
- * (rf: RenderFlags, ctx: any) => {
- * if (rf & 1) {
- * text(0, 'Hello');
- * text(1, 'Goodbye')
- * element(2, 'div');
- * }
- * if (rf & 2) {
- * advance(2); // Advance twice to the <div>.
- * property('title', 'test');
- * }
- * }
- * ```
- * @param delta Number of elements to advance forwards by.
- *
- * @codeGenApi
- */
- function ɵɵadvance(delta) {
- ngDevMode && assertGreaterThan(delta, 0, 'Can only advance forward');
- selectIndexInternal(getTView(), getLView(), getSelectedIndex() + delta, !!ngDevMode && isInCheckNoChangesMode());
- }
- function selectIndexInternal(tView, lView, index, checkNoChangesMode) {
- ngDevMode && assertIndexInDeclRange(lView, index);
- // Flush the initial hooks for elements in the view that have been added up to this point.
- // PERF WARNING: do NOT extract this to a separate function without running benchmarks
- if (!checkNoChangesMode) {
- const hooksInitPhaseCompleted = (lView[FLAGS] & 3 /* LViewFlags.InitPhaseStateMask */) === 3 /* InitPhaseState.InitPhaseCompleted */;
- if (hooksInitPhaseCompleted) {
- const preOrderCheckHooks = tView.preOrderCheckHooks;
- if (preOrderCheckHooks !== null) {
- executeCheckHooks(lView, preOrderCheckHooks, index);
- }
- }
- else {
- const preOrderHooks = tView.preOrderHooks;
- if (preOrderHooks !== null) {
- executeInitAndCheckHooks(lView, preOrderHooks, 0 /* InitPhaseState.OnInitHooksToBeRun */, index);
- }
- }
- }
- // We must set the selected index *after* running the hooks, because hooks may have side-effects
- // that cause other template functions to run, thus updating the selected index, which is global
- // state. If we run `setSelectedIndex` *before* we run the hooks, in some cases the selected index
- // will be altered by the time we leave the `ɵɵadvance` instruction.
- setSelectedIndex(index);
- }
- function ɵɵdirectiveInject(token, flags = InjectFlags.Default) {
- const lView = getLView();
- // Fall back to inject() if view hasn't been created. This situation can happen in tests
- // if inject utilities are used before bootstrapping.
- if (lView === null) {
- // Verify that we will not get into infinite loop.
- ngDevMode && assertInjectImplementationNotEqual(ɵɵdirectiveInject);
- return ɵɵinject(token, flags);
- }
- const tNode = getCurrentTNode();
- const value = getOrCreateInjectable(tNode, lView, resolveForwardRef(token), flags);
- ngDevMode && emitInjectEvent(token, value, flags);
- return value;
- }
- /**
- * Throws an error indicating that a factory function could not be generated by the compiler for a
- * particular class.
- *
- * This instruction allows the actual error message to be optimized away when ngDevMode is turned
- * off, saving bytes of generated code while still providing a good experience in dev mode.
- *
- * The name of the class is not mentioned here, but will be in the generated factory function name
- * and thus in the stack trace.
- *
- * @codeGenApi
- */
- function ɵɵinvalidFactory() {
- const msg = ngDevMode ? `This constructor was not compatible with Dependency Injection.` : 'invalid';
- throw new Error(msg);
- }
- /**
- * Invoke `HostBindingsFunction`s for view.
- *
- * This methods executes `TView.hostBindingOpCodes`. It is used to execute the
- * `HostBindingsFunction`s associated with the current `LView`.
- *
- * @param tView Current `TView`.
- * @param lView Current `LView`.
- */
- function processHostBindingOpCodes(tView, lView) {
- const hostBindingOpCodes = tView.hostBindingOpCodes;
- if (hostBindingOpCodes === null)
- return;
- const consumer = getReactiveLViewConsumer(lView, REACTIVE_HOST_BINDING_CONSUMER);
- try {
- for (let i = 0; i < hostBindingOpCodes.length; i++) {
- const opCode = hostBindingOpCodes[i];
- if (opCode < 0) {
- // Negative numbers are element indexes.
- setSelectedIndex(~opCode);
- }
- else {
- // Positive numbers are NumberTuple which store bindingRootIndex and directiveIndex.
- const directiveIdx = opCode;
- const bindingRootIndx = hostBindingOpCodes[++i];
- const hostBindingFn = hostBindingOpCodes[++i];
- setBindingRootForHostBindings(bindingRootIndx, directiveIdx);
- consumer.dirty = false;
- const prevConsumer = consumerBeforeComputation(consumer);
- try {
- const context = lView[directiveIdx];
- hostBindingFn(2 /* RenderFlags.Update */, context);
- }
- finally {
- consumerAfterComputation(consumer, prevConsumer);
- }
- }
- }
- }
- finally {
- if (lView[REACTIVE_HOST_BINDING_CONSUMER] === null) {
- commitLViewConsumerIfHasProducers(lView, REACTIVE_HOST_BINDING_CONSUMER);
- }
- setSelectedIndex(-1);
- }
- }
- function createLView(parentLView, tView, context, flags, host, tHostNode, environment, renderer, injector, embeddedViewInjector, hydrationInfo) {
- const lView = tView.blueprint.slice();
- lView[HOST] = host;
- lView[FLAGS] = flags | 4 /* LViewFlags.CreationMode */ | 128 /* LViewFlags.Attached */ | 8 /* LViewFlags.FirstLViewPass */;
- if (embeddedViewInjector !== null ||
- (parentLView && (parentLView[FLAGS] & 2048 /* LViewFlags.HasEmbeddedViewInjector */))) {
- lView[FLAGS] |= 2048 /* LViewFlags.HasEmbeddedViewInjector */;
- }
- resetPreOrderHookFlags(lView);
- ngDevMode && tView.declTNode && parentLView && assertTNodeForLView(tView.declTNode, parentLView);
- lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
- lView[CONTEXT] = context;
- lView[ENVIRONMENT] = (environment || parentLView && parentLView[ENVIRONMENT]);
- ngDevMode && assertDefined(lView[ENVIRONMENT], 'LViewEnvironment is required');
- lView[RENDERER] = (renderer || parentLView && parentLView[RENDERER]);
- ngDevMode && assertDefined(lView[RENDERER], 'Renderer is required');
- lView[INJECTOR$1] = injector || parentLView && parentLView[INJECTOR$1] || null;
- lView[T_HOST] = tHostNode;
- lView[ID] = getUniqueLViewId();
- lView[HYDRATION] = hydrationInfo;
- lView[EMBEDDED_VIEW_INJECTOR] = embeddedViewInjector;
- ngDevMode &&
- assertEqual(tView.type == 2 /* TViewType.Embedded */ ? parentLView !== null : true, true, 'Embedded views must have parentLView');
- lView[DECLARATION_COMPONENT_VIEW] =
- tView.type == 2 /* TViewType.Embedded */ ? parentLView[DECLARATION_COMPONENT_VIEW] : lView;
- return lView;
- }
- function getOrCreateTNode(tView, index, type, name, attrs) {
- ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
- // `view_engine_compatibility` for additional context.
- assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
- // Keep this function short, so that the VM will inline it.
- ngDevMode && assertPureTNodeType(type);
- let tNode = tView.data[index];
- if (tNode === null) {
- tNode = createTNodeAtIndex(tView, index, type, name, attrs);
- if (isInI18nBlock()) {
- // If we are in i18n block then all elements should be pre declared through `Placeholder`
- // See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
- // If the `TNode` was not pre-declared than it means it was not mentioned which means it was
- // removed, so we mark it as detached.
- tNode.flags |= 32 /* TNodeFlags.isDetached */;
- }
- }
- else if (tNode.type & 64 /* TNodeType.Placeholder */) {
- tNode.type = type;
- tNode.value = name;
- tNode.attrs = attrs;
- const parent = getCurrentParentTNode();
- tNode.injectorIndex = parent === null ? -1 : parent.injectorIndex;
- ngDevMode && assertTNodeForTView(tNode, tView);
- ngDevMode && assertEqual(index, tNode.index, 'Expecting same index');
- }
- setCurrentTNode(tNode, true);
- return tNode;
- }
- function createTNodeAtIndex(tView, index, type, name, attrs) {
- const currentTNode = getCurrentTNodePlaceholderOk();
- const isParent = isCurrentTNodeParent();
- const parent = isParent ? currentTNode : currentTNode && currentTNode.parent;
- // Parents cannot cross component boundaries because components will be used in multiple places.
- const tNode = tView.data[index] =
- createTNode(tView, parent, type, index, name, attrs);
- // Assign a pointer to the first child node of a given view. The first node is not always the one
- // at index 0, in case of i18n, index 0 can be the instruction `i18nStart` and the first node has
- // the index 1 or more, so we can't just check node index.
- if (tView.firstChild === null) {
- tView.firstChild = tNode;
- }
- if (currentTNode !== null) {
- if (isParent) {
- // FIXME(misko): This logic looks unnecessarily complicated. Could we simplify?
- if (currentTNode.child == null && tNode.parent !== null) {
- // We are in the same view, which means we are adding content node to the parent view.
- currentTNode.child = tNode;
- }
- }
- else {
- if (currentTNode.next === null) {
- // In the case of i18n the `currentTNode` may already be linked, in which case we don't want
- // to break the links which i18n created.
- currentTNode.next = tNode;
- tNode.prev = currentTNode;
- }
- }
- }
- return tNode;
- }
- /**
- * When elements are created dynamically after a view blueprint is created (e.g. through
- * i18nApply()), we need to adjust the blueprint for future
- * template passes.
- *
- * @param tView `TView` associated with `LView`
- * @param lView The `LView` containing the blueprint to adjust
- * @param numSlotsToAlloc The number of slots to alloc in the LView, should be >0
- * @param initialValue Initial value to store in blueprint
- */
- function allocExpando(tView, lView, numSlotsToAlloc, initialValue) {
- if (numSlotsToAlloc === 0)
- return -1;
- if (ngDevMode) {
- assertFirstCreatePass(tView);
- assertSame(tView, lView[TVIEW], '`LView` must be associated with `TView`!');
- assertEqual(tView.data.length, lView.length, 'Expecting LView to be same size as TView');
- assertEqual(tView.data.length, tView.blueprint.length, 'Expecting Blueprint to be same size as TView');
- assertFirstUpdatePass(tView);
- }
- const allocIdx = lView.length;
- for (let i = 0; i < numSlotsToAlloc; i++) {
- lView.push(initialValue);
- tView.blueprint.push(initialValue);
- tView.data.push(null);
- }
- return allocIdx;
- }
- function executeTemplate(tView, lView, templateFn, rf, context) {
- const consumer = getReactiveLViewConsumer(lView, REACTIVE_TEMPLATE_CONSUMER);
- const prevSelectedIndex = getSelectedIndex();
- const isUpdatePhase = rf & 2 /* RenderFlags.Update */;
- try {
- setSelectedIndex(-1);
- if (isUpdatePhase && lView.length > HEADER_OFFSET) {
- // When we're updating, inherently select 0 so we don't
- // have to generate that instruction for most update blocks.
- selectIndexInternal(tView, lView, HEADER_OFFSET, !!ngDevMode && isInCheckNoChangesMode());
- }
- const preHookType = isUpdatePhase ? 2 /* ProfilerEvent.TemplateUpdateStart */ : 0 /* ProfilerEvent.TemplateCreateStart */;
- profiler(preHookType, context);
- const effectiveConsumer = isUpdatePhase ? consumer : null;
- const prevConsumer = consumerBeforeComputation(effectiveConsumer);
- try {
- if (effectiveConsumer !== null) {
- effectiveConsumer.dirty = false;
- }
- templateFn(rf, context);
- }
- finally {
- consumerAfterComputation(effectiveConsumer, prevConsumer);
- }
- }
- finally {
- if (isUpdatePhase && lView[REACTIVE_TEMPLATE_CONSUMER] === null) {
- commitLViewConsumerIfHasProducers(lView, REACTIVE_TEMPLATE_CONSUMER);
- }
- setSelectedIndex(prevSelectedIndex);
- const postHookType = isUpdatePhase ? 3 /* ProfilerEvent.TemplateUpdateEnd */ : 1 /* ProfilerEvent.TemplateCreateEnd */;
- profiler(postHookType, context);
- }
- }
- //////////////////////////
- //// Element
- //////////////////////////
- function executeContentQueries(tView, tNode, lView) {
- if (isContentQueryHost(tNode)) {
- const prevConsumer = setActiveConsumer(null);
- try {
- const start = tNode.directiveStart;
- const end = tNode.directiveEnd;
- for (let directiveIndex = start; directiveIndex < end; directiveIndex++) {
- const def = tView.data[directiveIndex];
- if (def.contentQueries) {
- def.contentQueries(1 /* RenderFlags.Create */, lView[directiveIndex], directiveIndex);
- }
- }
- }
- finally {
- setActiveConsumer(prevConsumer);
- }
- }
- }
- /**
- * Creates directive instances.
- */
- function createDirectivesInstances(tView, lView, tNode) {
- if (!getBindingsEnabled())
- return;
- instantiateAllDirectives(tView, lView, tNode, getNativeByTNode(tNode, lView));
- if ((tNode.flags & 64 /* TNodeFlags.hasHostBindings */) === 64 /* TNodeFlags.hasHostBindings */) {
- invokeDirectivesHostBindings(tView, lView, tNode);
- }
- }
- /**
- * Takes a list of local names and indices and pushes the resolved local variable values
- * to LView in the same order as they are loaded in the template with load().
- */
- function saveResolvedLocalsInData(viewData, tNode, localRefExtractor = getNativeByTNode) {
- const localNames = tNode.localNames;
- if (localNames !== null) {
- let localIndex = tNode.index + 1;
- for (let i = 0; i < localNames.length; i += 2) {
- const index = localNames[i + 1];
- const value = index === -1 ?
- localRefExtractor(tNode, viewData) :
- viewData[index];
- viewData[localIndex++] = value;
- }
- }
- }
- /**
- * Gets TView from a template function or creates a new TView
- * if it doesn't already exist.
- *
- * @param def ComponentDef
- * @returns TView
- */
- function getOrCreateComponentTView(def) {
- const tView = def.tView;
- // Create a TView if there isn't one, or recreate it if the first create pass didn't
- // complete successfully since we can't know for sure whether it's in a usable shape.
- if (tView === null || tView.incompleteFirstPass) {
- // Declaration node here is null since this function is called when we dynamically create a
- // component and hence there is no declaration.
- const declTNode = null;
- return def.tView = createTView(1 /* TViewType.Component */, declTNode, def.template, def.decls, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery, def.schemas, def.consts, def.id);
- }
- return tView;
- }
- /**
- * Creates a TView instance
- *
- * @param type Type of `TView`.
- * @param declTNode Declaration location of this `TView`.
- * @param templateFn Template function
- * @param decls The number of nodes, local refs, and pipes in this template
- * @param directives Registry of directives for this view
- * @param pipes Registry of pipes for this view
- * @param viewQuery View queries for this view
- * @param schemas Schemas for this view
- * @param consts Constants for this view
- */
- function createTView(type, declTNode, templateFn, decls, vars, directives, pipes, viewQuery, schemas, constsOrFactory, ssrId) {
- ngDevMode && ngDevMode.tView++;
- const bindingStartIndex = HEADER_OFFSET + decls;
- // This length does not yet contain host bindings from child directives because at this point,
- // we don't know which directives are active on this template. As soon as a directive is matched
- // that has a host binding, we will update the blueprint with that def's hostVars count.
- const initialViewLength = bindingStartIndex + vars;
- const blueprint = createViewBlueprint(bindingStartIndex, initialViewLength);
- const consts = typeof constsOrFactory === 'function' ? constsOrFactory() : constsOrFactory;
- const tView = blueprint[TVIEW] = {
- type: type,
- blueprint: blueprint,
- template: templateFn,
- queries: null,
- viewQuery: viewQuery,
- declTNode: declTNode,
- data: blueprint.slice().fill(null, bindingStartIndex),
- bindingStartIndex: bindingStartIndex,
- expandoStartIndex: initialViewLength,
- hostBindingOpCodes: null,
- firstCreatePass: true,
- firstUpdatePass: true,
- staticViewQueries: false,
- staticContentQueries: false,
- preOrderHooks: null,
- preOrderCheckHooks: null,
- contentHooks: null,
- contentCheckHooks: null,
- viewHooks: null,
- viewCheckHooks: null,
- destroyHooks: null,
- cleanup: null,
- contentQueries: null,
- components: null,
- directiveRegistry: typeof directives === 'function' ? directives() : directives,
- pipeRegistry: typeof pipes === 'function' ? pipes() : pipes,
- firstChild: null,
- schemas: schemas,
- consts: consts,
- incompleteFirstPass: false,
- ssrId,
- };
- if (ngDevMode) {
- // For performance reasons it is important that the tView retains the same shape during runtime.
- // (To make sure that all of the code is monomorphic.) For this reason we seal the object to
- // prevent class transitions.
- Object.seal(tView);
- }
- return tView;
- }
- function createViewBlueprint(bindingStartIndex, initialViewLength) {
- const blueprint = [];
- for (let i = 0; i < initialViewLength; i++) {
- blueprint.push(i < bindingStartIndex ? null : NO_CHANGE);
- }
- return blueprint;
- }
- /**
- * Locates the host native element, used for bootstrapping existing nodes into rendering pipeline.
- *
- * @param renderer the renderer used to locate the element.
- * @param elementOrSelector Render element or CSS selector to locate the element.
- * @param encapsulation View Encapsulation defined for component that requests host element.
- * @param injector Root view injector instance.
- */
- function locateHostElement(renderer, elementOrSelector, encapsulation, injector) {
- // Note: we use default value for the `PRESERVE_HOST_CONTENT` here even though it's a
- // tree-shakable one (providedIn:'root'). This code path can be triggered during dynamic
- // component creation (after calling ViewContainerRef.createComponent) when an injector
- // instance can be provided. The injector instance might be disconnected from the main DI
- // tree, thus the `PRESERVE_HOST_CONTENT` would not be able to instantiate. In this case, the
- // default value will be used.
- const preserveHostContent = injector.get(PRESERVE_HOST_CONTENT, PRESERVE_HOST_CONTENT_DEFAULT);
- // When using native Shadow DOM, do not clear host element to allow native slot
- // projection.
- const preserveContent = preserveHostContent || encapsulation === ViewEncapsulation.ShadowDom;
- const rootElement = renderer.selectRootElement(elementOrSelector, preserveContent);
- applyRootElementTransform(rootElement);
- return rootElement;
- }
- /**
- * Applies any root element transformations that are needed. If hydration is enabled,
- * this will process corrupted text nodes.
- *
- * @param rootElement the app root HTML Element
- */
- function applyRootElementTransform(rootElement) {
- _applyRootElementTransformImpl(rootElement);
- }
- /**
- * Reference to a function that applies transformations to the root HTML element
- * of an app. When hydration is enabled, this processes any corrupt text nodes
- * so they are properly hydratable on the client.
- *
- * @param rootElement the app root HTML Element
- */
- let _applyRootElementTransformImpl = (rootElement) => null;
- /**
- * Processes text node markers before hydration begins. This replaces any special comment
- * nodes that were added prior to serialization are swapped out to restore proper text
- * nodes before hydration.
- *
- * @param rootElement the app root HTML Element
- */
- function applyRootElementTransformImpl(rootElement) {
- if (hasSkipHydrationAttrOnRElement(rootElement)) {
- // Handle a situation when the `ngSkipHydration` attribute is applied
- // to the root node of an application. In this case, we should clear
- // the contents and render everything from scratch.
- clearElementContents(rootElement);
- }
- else {
- processTextNodeMarkersBeforeHydration(rootElement);
- }
- }
- /**
- * Sets the implementation for the `applyRootElementTransform` function.
- */
- function enableApplyRootElementTransformImpl() {
- _applyRootElementTransformImpl = applyRootElementTransformImpl;
- }
- /**
- * Saves context for this cleanup function in LView.cleanupInstances.
- *
- * On the first template pass, saves in TView:
- * - Cleanup function
- * - Index of context we just saved in LView.cleanupInstances
- */
- function storeCleanupWithContext(tView, lView, context, cleanupFn) {
- const lCleanup = getOrCreateLViewCleanup(lView);
- // Historically the `storeCleanupWithContext` was used to register both framework-level and
- // user-defined cleanup callbacks, but over time those two types of cleanups were separated.
- // This dev mode checks assures that user-level cleanup callbacks are _not_ stored in data
- // structures reserved for framework-specific hooks.
- ngDevMode &&
- assertDefined(context, 'Cleanup context is mandatory when registering framework-level destroy hooks');
- lCleanup.push(context);
- if (tView.firstCreatePass) {
- getOrCreateTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1);
- }
- else {
- // Make sure that no new framework-level cleanup functions are registered after the first
- // template pass is done (and TView data structures are meant to fully constructed).
- if (ngDevMode) {
- Object.freeze(getOrCreateTViewCleanup(tView));
- }
- }
- }
- function createTNode(tView, tParent, type, index, value, attrs) {
- ngDevMode && index !== 0 && // 0 are bogus nodes and they are OK. See `createContainerRef` in
- // `view_engine_compatibility` for additional context.
- assertGreaterThanOrEqual(index, HEADER_OFFSET, 'TNodes can\'t be in the LView header.');
- ngDevMode && assertNotSame(attrs, undefined, '\'undefined\' is not valid value for \'attrs\'');
- ngDevMode && ngDevMode.tNode++;
- ngDevMode && tParent && assertTNodeForTView(tParent, tView);
- let injectorIndex = tParent ? tParent.injectorIndex : -1;
- let flags = 0;
- if (isInSkipHydrationBlock$1()) {
- flags |= 128 /* TNodeFlags.inSkipHydrationBlock */;
- }
- const tNode = {
- type,
- index,
- insertBeforeIndex: null,
- injectorIndex,
- directiveStart: -1,
- directiveEnd: -1,
- directiveStylingLast: -1,
- componentOffset: -1,
- propertyBindings: null,
- flags,
- providerIndexes: 0,
- value: value,
- attrs: attrs,
- mergedAttrs: null,
- localNames: null,
- initialInputs: undefined,
- inputs: null,
- outputs: null,
- tView: null,
- next: null,
- prev: null,
- projectionNext: null,
- child: null,
- parent: tParent,
- projection: null,
- styles: null,
- stylesWithoutHost: null,
- residualStyles: undefined,
- classes: null,
- classesWithoutHost: null,
- residualClasses: undefined,
- classBindings: 0,
- styleBindings: 0,
- };
- if (ngDevMode) {
- // For performance reasons it is important that the tNode retains the same shape during runtime.
- // (To make sure that all of the code is monomorphic.) For this reason we seal the object to
- // prevent class transitions.
- Object.seal(tNode);
- }
- return tNode;
- }
- /**
- * Generates the `PropertyAliases` data structure from the provided input/output mapping.
- * @param aliasMap Input/output mapping from the directive definition.
- * @param directiveIndex Index of the directive.
- * @param propertyAliases Object in which to store the results.
- * @param hostDirectiveAliasMap Object used to alias or filter out properties for host directives.
- * If the mapping is provided, it'll act as an allowlist, as well as a mapping of what public
- * name inputs/outputs should be exposed under.
- */
- function generatePropertyAliases(aliasMap, directiveIndex, propertyAliases, hostDirectiveAliasMap) {
- for (let publicName in aliasMap) {
- if (aliasMap.hasOwnProperty(publicName)) {
- propertyAliases = propertyAliases === null ? {} : propertyAliases;
- const internalName = aliasMap[publicName];
- // If there are no host directive mappings, we want to remap using the alias map from the
- // definition itself. If there is an alias map, it has two functions:
- // 1. It serves as an allowlist of bindings that are exposed by the host directives. Only the
- // ones inside the host directive map will be exposed on the host.
- // 2. The public name of the property is aliased using the host directive alias map, rather
- // than the alias map from the definition.
- if (hostDirectiveAliasMap === null) {
- addPropertyAlias(propertyAliases, directiveIndex, publicName, internalName);
- }
- else if (hostDirectiveAliasMap.hasOwnProperty(publicName)) {
- addPropertyAlias(propertyAliases, directiveIndex, hostDirectiveAliasMap[publicName], internalName);
- }
- }
- }
- return propertyAliases;
- }
- function addPropertyAlias(propertyAliases, directiveIndex, publicName, internalName) {
- if (propertyAliases.hasOwnProperty(publicName)) {
- propertyAliases[publicName].push(directiveIndex, internalName);
- }
- else {
- propertyAliases[publicName] = [directiveIndex, internalName];
- }
- }
- /**
- * Initializes data structures required to work with directive inputs and outputs.
- * Initialization is done for all directives matched on a given TNode.
- */
- function initializeInputAndOutputAliases(tView, tNode, hostDirectiveDefinitionMap) {
- ngDevMode && assertFirstCreatePass(tView);
- const start = tNode.directiveStart;
- const end = tNode.directiveEnd;
- const tViewData = tView.data;
- const tNodeAttrs = tNode.attrs;
- const inputsFromAttrs = [];
- let inputsStore = null;
- let outputsStore = null;
- for (let directiveIndex = start; directiveIndex < end; directiveIndex++) {
- const directiveDef = tViewData[directiveIndex];
- const aliasData = hostDirectiveDefinitionMap ? hostDirectiveDefinitionMap.get(directiveDef) : null;
- const aliasedInputs = aliasData ? aliasData.inputs : null;
- const aliasedOutputs = aliasData ? aliasData.outputs : null;
- inputsStore =
- generatePropertyAliases(directiveDef.inputs, directiveIndex, inputsStore, aliasedInputs);
- outputsStore =
- generatePropertyAliases(directiveDef.outputs, directiveIndex, outputsStore, aliasedOutputs);
- // Do not use unbound attributes as inputs to structural directives, since structural
- // directive inputs can only be set using microsyntax (e.g. `<div *dir="exp">`).
- // TODO(FW-1930): microsyntax expressions may also contain unbound/static attributes, which
- // should be set for inline templates.
- const initialInputs = (inputsStore !== null && tNodeAttrs !== null && !isInlineTemplate(tNode)) ?
- generateInitialInputs(inputsStore, directiveIndex, tNodeAttrs) :
- null;
- inputsFromAttrs.push(initialInputs);
- }
- if (inputsStore !== null) {
- if (inputsStore.hasOwnProperty('class')) {
- tNode.flags |= 8 /* TNodeFlags.hasClassInput */;
- }
- if (inputsStore.hasOwnProperty('style')) {
- tNode.flags |= 16 /* TNodeFlags.hasStyleInput */;
- }
- }
- tNode.initialInputs = inputsFromAttrs;
- tNode.inputs = inputsStore;
- tNode.outputs = outputsStore;
- }
- /**
- * Mapping between attributes names that don't correspond to their element property names.
- *
- * Performance note: this function is written as a series of if checks (instead of, say, a property
- * object lookup) for performance reasons - the series of `if` checks seems to be the fastest way of
- * mapping property names. Do NOT change without benchmarking.
- *
- * Note: this mapping has to be kept in sync with the equally named mapping in the template
- * type-checking machinery of ngtsc.
- */
- function mapPropName(name) {
- if (name === 'class')
- return 'className';
- if (name === 'for')
- return 'htmlFor';
- if (name === 'formaction')
- return 'formAction';
- if (name === 'innerHtml')
- return 'innerHTML';
- if (name === 'readonly')
- return 'readOnly';
- if (name === 'tabindex')
- return 'tabIndex';
- return name;
- }
- function elementPropertyInternal(tView, tNode, lView, propName, value, renderer, sanitizer, nativeOnly) {
- ngDevMode && assertNotSame(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
- const element = getNativeByTNode(tNode, lView);
- let inputData = tNode.inputs;
- let dataValue;
- if (!nativeOnly && inputData != null && (dataValue = inputData[propName])) {
- setInputsForProperty(tView, lView, dataValue, propName, value);
- if (isComponentHost(tNode))
- markDirtyIfOnPush(lView, tNode.index);
- if (ngDevMode) {
- setNgReflectProperties(lView, element, tNode.type, dataValue, value);
- }
- }
- else if (tNode.type & 3 /* TNodeType.AnyRNode */) {
- propName = mapPropName(propName);
- if (ngDevMode) {
- validateAgainstEventProperties(propName);
- if (!isPropertyValid(element, propName, tNode.value, tView.schemas)) {
- handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
- }
- ngDevMode.rendererSetProperty++;
- }
- // It is assumed that the sanitizer is only added when the compiler determines that the
- // property is risky, so sanitization can be done without further checks.
- value = sanitizer != null ? sanitizer(value, tNode.value || '', propName) : value;
- renderer.setProperty(element, propName, value);
- }
- else if (tNode.type & 12 /* TNodeType.AnyContainer */) {
- // If the node is a container and the property didn't
- // match any of the inputs or schemas we should throw.
- if (ngDevMode && !matchingSchemas(tView.schemas, tNode.value)) {
- handleUnknownPropertyError(propName, tNode.value, tNode.type, lView);
- }
- }
- }
- /** If node is an OnPush component, marks its LView dirty. */
- function markDirtyIfOnPush(lView, viewIndex) {
- ngDevMode && assertLView(lView);
- const childComponentLView = getComponentLViewByIndex(viewIndex, lView);
- if (!(childComponentLView[FLAGS] & 16 /* LViewFlags.CheckAlways */)) {
- childComponentLView[FLAGS] |= 64 /* LViewFlags.Dirty */;
- }
- }
- function setNgReflectProperty(lView, element, type, attrName, value) {
- const renderer = lView[RENDERER];
- attrName = normalizeDebugBindingName(attrName);
- const debugValue = normalizeDebugBindingValue(value);
- if (type & 3 /* TNodeType.AnyRNode */) {
- if (value == null) {
- renderer.removeAttribute(element, attrName);
- }
- else {
- renderer.setAttribute(element, attrName, debugValue);
- }
- }
- else {
- const textContent = escapeCommentText(`bindings=${JSON.stringify({ [attrName]: debugValue }, null, 2)}`);
- renderer.setValue(element, textContent);
- }
- }
- function setNgReflectProperties(lView, element, type, dataValue, value) {
- if (type & (3 /* TNodeType.AnyRNode */ | 4 /* TNodeType.Container */)) {
- /**
- * dataValue is an array containing runtime input or output names for the directives:
- * i+0: directive instance index
- * i+1: privateName
- *
- * e.g. [0, 'change', 'change-minified']
- * we want to set the reflected property with the privateName: dataValue[i+1]
- */
- for (let i = 0; i < dataValue.length; i += 2) {
- setNgReflectProperty(lView, element, type, dataValue[i + 1], value);
- }
- }
- }
- /**
- * Resolve the matched directives on a node.
- */
- function resolveDirectives(tView, lView, tNode, localRefs) {
- // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
- // tsickle.
- ngDevMode && assertFirstCreatePass(tView);
- if (getBindingsEnabled()) {
- const exportsMap = localRefs === null ? null : { '': -1 };
- const matchResult = findDirectiveDefMatches(tView, tNode);
- let directiveDefs;
- let hostDirectiveDefs;
- if (matchResult === null) {
- directiveDefs = hostDirectiveDefs = null;
- }
- else {
- [directiveDefs, hostDirectiveDefs] = matchResult;
- }
- if (directiveDefs !== null) {
- initializeDirectives(tView, lView, tNode, directiveDefs, exportsMap, hostDirectiveDefs);
- }
- if (exportsMap)
- cacheMatchingLocalNames(tNode, localRefs, exportsMap);
- }
- // Merge the template attrs last so that they have the highest priority.
- tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, tNode.attrs);
- }
- /** Initializes the data structures necessary for a list of directives to be instantiated. */
- function initializeDirectives(tView, lView, tNode, directives, exportsMap, hostDirectiveDefs) {
- ngDevMode && assertFirstCreatePass(tView);
- // Publishes the directive types to DI so they can be injected. Needs to
- // happen in a separate pass before the TNode flags have been initialized.
- for (let i = 0; i < directives.length; i++) {
- diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, lView), tView, directives[i].type);
- }
- initTNodeFlags(tNode, tView.data.length, directives.length);
- // When the same token is provided by several directives on the same node, some rules apply in
- // the viewEngine:
- // - viewProviders have priority over providers
- // - the last directive in NgModule.declarations has priority over the previous one
- // So to match these rules, the order in which providers are added in the arrays is very
- // important.
- for (let i = 0; i < directives.length; i++) {
- const def = directives[i];
- if (def.providersResolver)
- def.providersResolver(def);
- }
- let preOrderHooksFound = false;
- let preOrderCheckHooksFound = false;
- let directiveIdx = allocExpando(tView, lView, directives.length, null);
- ngDevMode &&
- assertSame(directiveIdx, tNode.directiveStart, 'TNode.directiveStart should point to just allocated space');
- for (let i = 0; i < directives.length; i++) {
- const def = directives[i];
- // Merge the attrs in the order of matches. This assumes that the first directive is the
- // component itself, so that the component has the least priority.
- tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
- configureViewWithDirective(tView, tNode, lView, directiveIdx, def);
- saveNameToExportMap(directiveIdx, def, exportsMap);
- if (def.contentQueries !== null)
- tNode.flags |= 4 /* TNodeFlags.hasContentQuery */;
- if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
- tNode.flags |= 64 /* TNodeFlags.hasHostBindings */;
- const lifeCycleHooks = def.type.prototype;
- // Only push a node index into the preOrderHooks array if this is the first
- // pre-order hook found on this node.
- if (!preOrderHooksFound &&
- (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngOnInit || lifeCycleHooks.ngDoCheck)) {
- // We will push the actual hook function into this array later during dir instantiation.
- // We cannot do it now because we must ensure hooks are registered in the same
- // order that directives are created (i.e. injection order).
- (tView.preOrderHooks ??= []).push(tNode.index);
- preOrderHooksFound = true;
- }
- if (!preOrderCheckHooksFound && (lifeCycleHooks.ngOnChanges || lifeCycleHooks.ngDoCheck)) {
- (tView.preOrderCheckHooks ??= []).push(tNode.index);
- preOrderCheckHooksFound = true;
- }
- directiveIdx++;
- }
- initializeInputAndOutputAliases(tView, tNode, hostDirectiveDefs);
- }
- /**
- * Add `hostBindings` to the `TView.hostBindingOpCodes`.
- *
- * @param tView `TView` to which the `hostBindings` should be added.
- * @param tNode `TNode` the element which contains the directive
- * @param directiveIdx Directive index in view.
- * @param directiveVarsIdx Where will the directive's vars be stored
- * @param def `ComponentDef`/`DirectiveDef`, which contains the `hostVars`/`hostBindings` to add.
- */
- function registerHostBindingOpCodes(tView, tNode, directiveIdx, directiveVarsIdx, def) {
- ngDevMode && assertFirstCreatePass(tView);
- const hostBindings = def.hostBindings;
- if (hostBindings) {
- let hostBindingOpCodes = tView.hostBindingOpCodes;
- if (hostBindingOpCodes === null) {
- hostBindingOpCodes = tView.hostBindingOpCodes = [];
- }
- const elementIndx = ~tNode.index;
- if (lastSelectedElementIdx(hostBindingOpCodes) != elementIndx) {
- // Conditionally add select element so that we are more efficient in execution.
- // NOTE: this is strictly not necessary and it trades code size for runtime perf.
- // (We could just always add it.)
- hostBindingOpCodes.push(elementIndx);
- }
- hostBindingOpCodes.push(directiveIdx, directiveVarsIdx, hostBindings);
- }
- }
- /**
- * Returns the last selected element index in the `HostBindingOpCodes`
- *
- * For perf reasons we don't need to update the selected element index in `HostBindingOpCodes` only
- * if it changes. This method returns the last index (or '0' if not found.)
- *
- * Selected element index are only the ones which are negative.
- */
- function lastSelectedElementIdx(hostBindingOpCodes) {
- let i = hostBindingOpCodes.length;
- while (i > 0) {
- const value = hostBindingOpCodes[--i];
- if (typeof value === 'number' && value < 0) {
- return value;
- }
- }
- return 0;
- }
- /**
- * Instantiate all the directives that were previously resolved on the current node.
- */
- function instantiateAllDirectives(tView, lView, tNode, native) {
- const start = tNode.directiveStart;
- const end = tNode.directiveEnd;
- // The component view needs to be created before creating the node injector
- // since it is used to inject some special symbols like `ChangeDetectorRef`.
- if (isComponentHost(tNode)) {
- ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */);
- addComponentLogic(lView, tNode, tView.data[start + tNode.componentOffset]);
- }
- if (!tView.firstCreatePass) {
- getOrCreateNodeInjectorForNode(tNode, lView);
- }
- attachPatchData(native, lView);
- const initialInputs = tNode.initialInputs;
- for (let i = start; i < end; i++) {
- const def = tView.data[i];
- const directive = getNodeInjectable(lView, tView, i, tNode);
- attachPatchData(directive, lView);
- if (initialInputs !== null) {
- setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs);
- }
- if (isComponentDef(def)) {
- const componentView = getComponentLViewByIndex(tNode.index, lView);
- componentView[CONTEXT] = getNodeInjectable(lView, tView, i, tNode);
- }
- }
- }
- function invokeDirectivesHostBindings(tView, lView, tNode) {
- const start = tNode.directiveStart;
- const end = tNode.directiveEnd;
- const elementIndex = tNode.index;
- const currentDirectiveIndex = getCurrentDirectiveIndex();
- try {
- setSelectedIndex(elementIndex);
- for (let dirIndex = start; dirIndex < end; dirIndex++) {
- const def = tView.data[dirIndex];
- const directive = lView[dirIndex];
- setCurrentDirectiveIndex(dirIndex);
- if (def.hostBindings !== null || def.hostVars !== 0 || def.hostAttrs !== null) {
- invokeHostBindingsInCreationMode(def, directive);
- }
- }
- }
- finally {
- setSelectedIndex(-1);
- setCurrentDirectiveIndex(currentDirectiveIndex);
- }
- }
- /**
- * Invoke the host bindings in creation mode.
- *
- * @param def `DirectiveDef` which may contain the `hostBindings` function.
- * @param directive Instance of directive.
- */
- function invokeHostBindingsInCreationMode(def, directive) {
- if (def.hostBindings !== null) {
- def.hostBindings(1 /* RenderFlags.Create */, directive);
- }
- }
- /**
- * Matches the current node against all available selectors.
- * If a component is matched (at most one), it is returned in first position in the array.
- */
- function findDirectiveDefMatches(tView, tNode) {
- ngDevMode && assertFirstCreatePass(tView);
- ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */);
- const registry = tView.directiveRegistry;
- let matches = null;
- let hostDirectiveDefs = null;
- if (registry) {
- for (let i = 0; i < registry.length; i++) {
- const def = registry[i];
- if (isNodeMatchingSelectorList(tNode, def.selectors, /* isProjectionMode */ false)) {
- matches || (matches = []);
- if (isComponentDef(def)) {
- if (ngDevMode) {
- assertTNodeType(tNode, 2 /* TNodeType.Element */, `"${tNode.value}" tags cannot be used as component hosts. ` +
- `Please use a different tag to activate the ${stringify(def.type)} component.`);
- if (isComponentHost(tNode)) {
- throwMultipleComponentError(tNode, matches.find(isComponentDef).type, def.type);
- }
- }
- // Components are inserted at the front of the matches array so that their lifecycle
- // hooks run before any directive lifecycle hooks. This appears to be for ViewEngine
- // compatibility. This logic doesn't make sense with host directives, because it
- // would allow the host directives to undo any overrides the host may have made.
- // To handle this case, the host directives of components are inserted at the beginning
- // of the array, followed by the component. As such, the insertion order is as follows:
- // 1. Host directives belonging to the selector-matched component.
- // 2. Selector-matched component.
- // 3. Host directives belonging to selector-matched directives.
- // 4. Selector-matched directives.
- if (def.findHostDirectiveDefs !== null) {
- const hostDirectiveMatches = [];
- hostDirectiveDefs = hostDirectiveDefs || new Map();
- def.findHostDirectiveDefs(def, hostDirectiveMatches, hostDirectiveDefs);
- // Add all host directives declared on this component, followed by the component itself.
- // Host directives should execute first so the host has a chance to override changes
- // to the DOM made by them.
- matches.unshift(...hostDirectiveMatches, def);
- // Component is offset starting from the beginning of the host directives array.
- const componentOffset = hostDirectiveMatches.length;
- markAsComponentHost(tView, tNode, componentOffset);
- }
- else {
- // No host directives on this component, just add the
- // component def to the beginning of the matches.
- matches.unshift(def);
- markAsComponentHost(tView, tNode, 0);
- }
- }
- else {
- // Append any host directives to the matches first.
- hostDirectiveDefs = hostDirectiveDefs || new Map();
- def.findHostDirectiveDefs?.(def, matches, hostDirectiveDefs);
- matches.push(def);
- }
- }
- }
- }
- return matches === null ? null : [matches, hostDirectiveDefs];
- }
- /**
- * Marks a given TNode as a component's host. This consists of:
- * - setting the component offset on the TNode.
- * - storing index of component's host element so it will be queued for view refresh during CD.
- */
- function markAsComponentHost(tView, hostTNode, componentOffset) {
- ngDevMode && assertFirstCreatePass(tView);
- ngDevMode && assertGreaterThan(componentOffset, -1, 'componentOffset must be great than -1');
- hostTNode.componentOffset = componentOffset;
- (tView.components ??= []).push(hostTNode.index);
- }
- /** Caches local names and their matching directive indices for query and template lookups. */
- function cacheMatchingLocalNames(tNode, localRefs, exportsMap) {
- if (localRefs) {
- const localNames = tNode.localNames = [];
- // Local names must be stored in tNode in the same order that localRefs are defined
- // in the template to ensure the data is loaded in the same slots as their refs
- // in the template (for template queries).
- for (let i = 0; i < localRefs.length; i += 2) {
- const index = exportsMap[localRefs[i + 1]];
- if (index == null)
- throw new RuntimeError(-301 /* RuntimeErrorCode.EXPORT_NOT_FOUND */, ngDevMode && `Export of name '${localRefs[i + 1]}' not found!`);
- localNames.push(localRefs[i], index);
- }
- }
- }
- /**
- * Builds up an export map as directives are created, so local refs can be quickly mapped
- * to their directive instances.
- */
- function saveNameToExportMap(directiveIdx, def, exportsMap) {
- if (exportsMap) {
- if (def.exportAs) {
- for (let i = 0; i < def.exportAs.length; i++) {
- exportsMap[def.exportAs[i]] = directiveIdx;
- }
- }
- if (isComponentDef(def))
- exportsMap[''] = directiveIdx;
- }
- }
- /**
- * Initializes the flags on the current node, setting all indices to the initial index,
- * the directive count to 0, and adding the isComponent flag.
- * @param index the initial index
- */
- function initTNodeFlags(tNode, index, numberOfDirectives) {
- ngDevMode &&
- assertNotEqual(numberOfDirectives, tNode.directiveEnd - tNode.directiveStart, 'Reached the max number of directives');
- tNode.flags |= 1 /* TNodeFlags.isDirectiveHost */;
- // When the first directive is created on a node, save the index
- tNode.directiveStart = index;
- tNode.directiveEnd = index + numberOfDirectives;
- tNode.providerIndexes = index;
- }
- /**
- * Setup directive for instantiation.
- *
- * We need to create a `NodeInjectorFactory` which is then inserted in both the `Blueprint` as well
- * as `LView`. `TView` gets the `DirectiveDef`.
- *
- * @param tView `TView`
- * @param tNode `TNode`
- * @param lView `LView`
- * @param directiveIndex Index where the directive will be stored in the Expando.
- * @param def `DirectiveDef`
- */
- function configureViewWithDirective(tView, tNode, lView, directiveIndex, def) {
- ngDevMode &&
- assertGreaterThanOrEqual(directiveIndex, HEADER_OFFSET, 'Must be in Expando section');
- tView.data[directiveIndex] = def;
- const directiveFactory = def.factory || (def.factory = getFactoryDef(def.type, true));
- // Even though `directiveFactory` will already be using `ɵɵdirectiveInject` in its generated code,
- // we also want to support `inject()` directly from the directive constructor context so we set
- // `ɵɵdirectiveInject` as the inject implementation here too.
- const nodeInjectorFactory = new NodeInjectorFactory(directiveFactory, isComponentDef(def), ɵɵdirectiveInject);
- tView.blueprint[directiveIndex] = nodeInjectorFactory;
- lView[directiveIndex] = nodeInjectorFactory;
- registerHostBindingOpCodes(tView, tNode, directiveIndex, allocExpando(tView, lView, def.hostVars, NO_CHANGE), def);
- }
- function addComponentLogic(lView, hostTNode, def) {
- const native = getNativeByTNode(hostTNode, lView);
- const tView = getOrCreateComponentTView(def);
- // Only component views should be added to the view tree directly. Embedded views are
- // accessed through their containers because they may be removed / re-added later.
- const rendererFactory = lView[ENVIRONMENT].rendererFactory;
- let lViewFlags = 16 /* LViewFlags.CheckAlways */;
- if (def.signals) {
- lViewFlags = 4096 /* LViewFlags.SignalView */;
- }
- else if (def.onPush) {
- lViewFlags = 64 /* LViewFlags.Dirty */;
- }
- const componentView = addToViewTree(lView, createLView(lView, tView, null, lViewFlags, native, hostTNode, null, rendererFactory.createRenderer(native, def), null, null, null));
- // Component view will always be created before any injected LContainers,
- // so this is a regular element, wrap it with the component view
- lView[hostTNode.index] = componentView;
- }
- function elementAttributeInternal(tNode, lView, name, value, sanitizer, namespace) {
- if (ngDevMode) {
- assertNotSame(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
- validateAgainstEventAttributes(name);
- assertTNodeType(tNode, 2 /* TNodeType.Element */, `Attempted to set attribute \`${name}\` on a container node. ` +
- `Host bindings are not valid on ng-container or ng-template.`);
- }
- const element = getNativeByTNode(tNode, lView);
- setElementAttribute(lView[RENDERER], element, namespace, tNode.value, name, value, sanitizer);
- }
- function setElementAttribute(renderer, element, namespace, tagName, name, value, sanitizer) {
- if (value == null) {
- ngDevMode && ngDevMode.rendererRemoveAttribute++;
- renderer.removeAttribute(element, name, namespace);
- }
- else {
- ngDevMode && ngDevMode.rendererSetAttribute++;
- const strValue = sanitizer == null ? renderStringify(value) : sanitizer(value, tagName || '', name);
- renderer.setAttribute(element, name, strValue, namespace);
- }
- }
- /**
- * Sets initial input properties on directive instances from attribute data
- *
- * @param lView Current LView that is being processed.
- * @param directiveIndex Index of the directive in directives array
- * @param instance Instance of the directive on which to set the initial inputs
- * @param def The directive def that contains the list of inputs
- * @param tNode The static data for this node
- */
- function setInputsFromAttrs(lView, directiveIndex, instance, def, tNode, initialInputData) {
- const initialInputs = initialInputData[directiveIndex];
- if (initialInputs !== null) {
- for (let i = 0; i < initialInputs.length;) {
- const publicName = initialInputs[i++];
- const privateName = initialInputs[i++];
- const value = initialInputs[i++];
- writeToDirectiveInput(def, instance, publicName, privateName, value);
- if (ngDevMode) {
- const nativeElement = getNativeByTNode(tNode, lView);
- setNgReflectProperty(lView, nativeElement, tNode.type, privateName, value);
- }
- }
- }
- }
- function writeToDirectiveInput(def, instance, publicName, privateName, value) {
- const prevConsumer = setActiveConsumer(null);
- try {
- const inputTransforms = def.inputTransforms;
- if (inputTransforms !== null && inputTransforms.hasOwnProperty(privateName)) {
- value = inputTransforms[privateName].call(instance, value);
- }
- if (def.setInput !== null) {
- def.setInput(instance, value, publicName, privateName);
- }
- else {
- instance[privateName] = value;
- }
- }
- finally {
- setActiveConsumer(prevConsumer);
- }
- }
- /**
- * Generates initialInputData for a node and stores it in the template's static storage
- * so subsequent template invocations don't have to recalculate it.
- *
- * initialInputData is an array containing values that need to be set as input properties
- * for directives on this node, but only once on creation. We need this array to support
- * the case where you set an @Input property of a directive using attribute-like syntax.
- * e.g. if you have a `name` @Input, you can set it once like this:
- *
- * <my-component name="Bess"></my-component>
- *
- * @param inputs Input alias map that was generated from the directive def inputs.
- * @param directiveIndex Index of the directive that is currently being processed.
- * @param attrs Static attrs on this node.
- */
- function generateInitialInputs(inputs, directiveIndex, attrs) {
- let inputsToStore = null;
- let i = 0;
- while (i < attrs.length) {
- const attrName = attrs[i];
- if (attrName === 0 /* AttributeMarker.NamespaceURI */) {
- // We do not allow inputs on namespaced attributes.
- i += 4;
- continue;
- }
- else if (attrName === 5 /* AttributeMarker.ProjectAs */) {
- // Skip over the `ngProjectAs` value.
- i += 2;
- continue;
- }
- // If we hit any other attribute markers, we're done anyway. None of those are valid inputs.
- if (typeof attrName === 'number')
- break;
- if (inputs.hasOwnProperty(attrName)) {
- if (inputsToStore === null)
- inputsToStore = [];
- // Find the input's public name from the input store. Note that we can be found easier
- // through the directive def, but we want to do it using the inputs store so that it can
- // account for host directive aliases.
- const inputConfig = inputs[attrName];
- for (let j = 0; j < inputConfig.length; j += 2) {
- if (inputConfig[j] === directiveIndex) {
- inputsToStore.push(attrName, inputConfig[j + 1], attrs[i + 1]);
- // A directive can't have multiple inputs with the same name so we can break here.
- break;
- }
- }
- }
- i += 2;
- }
- return inputsToStore;
- }
- //////////////////////////
- //// ViewContainer & View
- //////////////////////////
- /**
- * Creates a LContainer, either from a container instruction, or for a ViewContainerRef.
- *
- * @param hostNative The host element for the LContainer
- * @param hostTNode The host TNode for the LContainer
- * @param currentView The parent view of the LContainer
- * @param native The native comment element
- * @param isForViewContainerRef Optional a flag indicating the ViewContainerRef case
- * @returns LContainer
- */
- function createLContainer(hostNative, currentView, native, tNode) {
- ngDevMode && assertLView(currentView);
- const lContainer = [
- hostNative,
- true,
- false,
- currentView,
- null,
- 0,
- tNode,
- native,
- null,
- null,
- null, // dehydrated views
- ];
- ngDevMode &&
- assertEqual(lContainer.length, CONTAINER_HEADER_OFFSET, 'Should allocate correct number of slots for LContainer header.');
- return lContainer;
- }
- /** Refreshes all content queries declared by directives in a given view */
- function refreshContentQueries(tView, lView) {
- const contentQueries = tView.contentQueries;
- if (contentQueries !== null) {
- for (let i = 0; i < contentQueries.length; i += 2) {
- const queryStartIdx = contentQueries[i];
- const directiveDefIdx = contentQueries[i + 1];
- if (directiveDefIdx !== -1) {
- const directiveDef = tView.data[directiveDefIdx];
- ngDevMode && assertDefined(directiveDef, 'DirectiveDef not found.');
- ngDevMode &&
- assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
- setCurrentQueryIndex(queryStartIdx);
- directiveDef.contentQueries(2 /* RenderFlags.Update */, lView[directiveDefIdx], directiveDefIdx);
- }
- }
- }
- }
- /**
- * Adds LView or LContainer to the end of the current view tree.
- *
- * This structure will be used to traverse through nested views to remove listeners
- * and call onDestroy callbacks.
- *
- * @param lView The view where LView or LContainer should be added
- * @param adjustedHostIndex Index of the view's host node in LView[], adjusted for header
- * @param lViewOrLContainer The LView or LContainer to add to the view tree
- * @returns The state passed in
- */
- function addToViewTree(lView, lViewOrLContainer) {
- // TODO(benlesh/misko): This implementation is incorrect, because it always adds the LContainer
- // to the end of the queue, which means if the developer retrieves the LContainers from RNodes out
- // of order, the change detection will run out of order, as the act of retrieving the the
- // LContainer from the RNode is what adds it to the queue.
- if (lView[CHILD_HEAD]) {
- lView[CHILD_TAIL][NEXT] = lViewOrLContainer;
- }
- else {
- lView[CHILD_HEAD] = lViewOrLContainer;
- }
- lView[CHILD_TAIL] = lViewOrLContainer;
- return lViewOrLContainer;
- }
- ///////////////////////////////
- //// Change detection
- ///////////////////////////////
- function executeViewQueryFn(flags, viewQueryFn, component) {
- ngDevMode && assertDefined(viewQueryFn, 'View queries function to execute must be defined.');
- setCurrentQueryIndex(0);
- const prevConsumer = setActiveConsumer(null);
- try {
- viewQueryFn(flags, component);
- }
- finally {
- setActiveConsumer(prevConsumer);
- }
- }
- ///////////////////////////////
- //// Bindings & interpolations
- ///////////////////////////////
- /**
- * Stores meta-data for a property binding to be used by TestBed's `DebugElement.properties`.
- *
- * In order to support TestBed's `DebugElement.properties` we need to save, for each binding:
- * - a bound property name;
- * - a static parts of interpolated strings;
- *
- * A given property metadata is saved at the binding's index in the `TView.data` (in other words, a
- * property binding metadata will be stored in `TView.data` at the same index as a bound value in
- * `LView`). Metadata are represented as `INTERPOLATION_DELIMITER`-delimited string with the
- * following format:
- * - `propertyName` for bound properties;
- * - `propertyName�prefix�interpolation_static_part1�..interpolation_static_partN�suffix` for
- * interpolated properties.
- *
- * @param tData `TData` where meta-data will be saved;
- * @param tNode `TNode` that is a target of the binding;
- * @param propertyName bound property name;
- * @param bindingIndex binding index in `LView`
- * @param interpolationParts static interpolation parts (for property interpolations)
- */
- function storePropertyBindingMetadata(tData, tNode, propertyName, bindingIndex, ...interpolationParts) {
- // Binding meta-data are stored only the first time a given property instruction is processed.
- // Since we don't have a concept of the "first update pass" we need to check for presence of the
- // binding meta-data to decide if one should be stored (or if was stored already).
- if (tData[bindingIndex] === null) {
- if (tNode.inputs == null || !tNode.inputs[propertyName]) {
- const propBindingIdxs = tNode.propertyBindings || (tNode.propertyBindings = []);
- propBindingIdxs.push(bindingIndex);
- let bindingMetadata = propertyName;
- if (interpolationParts.length > 0) {
- bindingMetadata +=
- INTERPOLATION_DELIMITER + interpolationParts.join(INTERPOLATION_DELIMITER);
- }
- tData[bindingIndex] = bindingMetadata;
- }
- }
- }
- function getOrCreateLViewCleanup(view) {
- // top level variables should not be exported for performance reasons (PERF_NOTES.md)
- return view[CLEANUP] || (view[CLEANUP] = []);
- }
- function getOrCreateTViewCleanup(tView) {
- return tView.cleanup || (tView.cleanup = []);
- }
- /**
- * There are cases where the sub component's renderer needs to be included
- * instead of the current renderer (see the componentSyntheticHost* instructions).
- */
- function loadComponentRenderer(currentDef, tNode, lView) {
- // TODO(FW-2043): the `currentDef` is null when host bindings are invoked while creating root
- // component (see packages/core/src/render3/component.ts). This is not consistent with the process
- // of creating inner components, when current directive index is available in the state. In order
- // to avoid relying on current def being `null` (thus special-casing root component creation), the
- // process of creating root component should be unified with the process of creating inner
- // components.
- if (currentDef === null || isComponentDef(currentDef)) {
- lView = unwrapLView(lView[tNode.index]);
- }
- return lView[RENDERER];
- }
- /** Handles an error thrown in an LView. */
- function handleError(lView, error) {
- const injector = lView[INJECTOR$1];
- const errorHandler = injector ? injector.get(ErrorHandler, null) : null;
- errorHandler && errorHandler.handleError(error);
- }
- /**
- * Set the inputs of directives at the current node to corresponding value.
- *
- * @param tView The current TView
- * @param lView the `LView` which contains the directives.
- * @param inputs mapping between the public "input" name and privately-known,
- * possibly minified, property names to write to.
- * @param value Value to set.
- */
- function setInputsForProperty(tView, lView, inputs, publicName, value) {
- for (let i = 0; i < inputs.length;) {
- const index = inputs[i++];
- const privateName = inputs[i++];
- const instance = lView[index];
- ngDevMode && assertIndexInRange(lView, index);
- const def = tView.data[index];
- writeToDirectiveInput(def, instance, publicName, privateName, value);
- }
- }
- /**
- * Updates a text binding at a given index in a given LView.
- */
- function textBindingInternal(lView, index, value) {
- ngDevMode && assertString(value, 'Value should be a string');
- ngDevMode && assertNotSame(value, NO_CHANGE, 'value should not be NO_CHANGE');
- ngDevMode && assertIndexInRange(lView, index);
- const element = getNativeByIndex(index, lView);
- ngDevMode && assertDefined(element, 'native element should exist');
- updateTextNode(lView[RENDERER], element, value);
- }
- function renderComponent(hostLView, componentHostIdx) {
- ngDevMode && assertEqual(isCreationMode(hostLView), true, 'Should be run in creation mode');
- const componentView = getComponentLViewByIndex(componentHostIdx, hostLView);
- const componentTView = componentView[TVIEW];
- syncViewWithBlueprint(componentTView, componentView);
- const hostRNode = componentView[HOST];
- // Populate an LView with hydration info retrieved from the DOM via TransferState.
- if (hostRNode !== null && componentView[HYDRATION] === null) {
- componentView[HYDRATION] = retrieveHydrationInfo(hostRNode, componentView[INJECTOR$1]);
- }
- renderView(componentTView, componentView, componentView[CONTEXT]);
- }
- /**
- * Syncs an LView instance with its blueprint if they have gotten out of sync.
- *
- * Typically, blueprints and their view instances should always be in sync, so the loop here
- * will be skipped. However, consider this case of two components side-by-side:
- *
- * App template:
- * ```
- * <comp></comp>
- * <comp></comp>
- * ```
- *
- * The following will happen:
- * 1. App template begins processing.
- * 2. First <comp> is matched as a component and its LView is created.
- * 3. Second <comp> is matched as a component and its LView is created.
- * 4. App template completes processing, so it's time to check child templates.
- * 5. First <comp> template is checked. It has a directive, so its def is pushed to blueprint.
- * 6. Second <comp> template is checked. Its blueprint has been updated by the first
- * <comp> template, but its LView was created before this update, so it is out of sync.
- *
- * Note that embedded views inside ngFor loops will never be out of sync because these views
- * are processed as soon as they are created.
- *
- * @param tView The `TView` that contains the blueprint for syncing
- * @param lView The view to sync
- */
- function syncViewWithBlueprint(tView, lView) {
- for (let i = lView.length; i < tView.blueprint.length; i++) {
- lView.push(tView.blueprint[i]);
- }
- }
- /**
- * Processes a view in the creation mode. This includes a number of steps in a specific order:
- * - creating view query functions (if any);
- * - executing a template function in the creation mode;
- * - updating static queries (if any);
- * - creating child components defined in a given view.
- */
- function renderView(tView, lView, context) {
- ngDevMode && assertEqual(isCreationMode(lView), true, 'Should be run in creation mode');
- enterView(lView);
- try {
- const viewQuery = tView.viewQuery;
- if (viewQuery !== null) {
- executeViewQueryFn(1 /* RenderFlags.Create */, viewQuery, context);
- }
- // Execute a template associated with this view, if it exists. A template function might not be
- // defined for the root component views.
- const templateFn = tView.template;
- if (templateFn !== null) {
- executeTemplate(tView, lView, templateFn, 1 /* RenderFlags.Create */, context);
- }
- // This needs to be set before children are processed to support recursive components.
- // This must be set to false immediately after the first creation run because in an
- // ngFor loop, all the views will be created together before update mode runs and turns
- // off firstCreatePass. If we don't set it here, instances will perform directive
- // matching, etc again and again.
- if (tView.firstCreatePass) {
- tView.firstCreatePass = false;
- }
- // We resolve content queries specifically marked as `static` in creation mode. Dynamic
- // content queries are resolved during change detection (i.e. update mode), after embedded
- // views are refreshed (see block above).
- if (tView.staticContentQueries) {
- refreshContentQueries(tView, lView);
- }
- // We must materialize query results before child components are processed
- // in case a child component has projected a container. The LContainer needs
- // to exist so the embedded views are properly attached by the container.
- if (tView.staticViewQueries) {
- executeViewQueryFn(2 /* RenderFlags.Update */, tView.viewQuery, context);
- }
- // Render child component views.
- const components = tView.components;
- if (components !== null) {
- renderChildComponents(lView, components);
- }
- }
- catch (error) {
- // If we didn't manage to get past the first template pass due to
- // an error, mark the view as corrupted so we can try to recover.
- if (tView.firstCreatePass) {
- tView.incompleteFirstPass = true;
- tView.firstCreatePass = false;
- }
- throw error;
- }
- finally {
- lView[FLAGS] &= ~4 /* LViewFlags.CreationMode */;
- leaveView();
- }
- }
- /** Renders child components in the current view (creation mode). */
- function renderChildComponents(hostLView, components) {
- for (let i = 0; i < components.length; i++) {
- renderComponent(hostLView, components[i]);
- }
- }
- /**
- * Tracks all effects registered within a given application and runs them via `flush`.
- */
- class EffectManager {
- constructor() {
- this.all = new Set();
- this.queue = new Map();
- }
- create(effectFn, destroyRef, allowSignalWrites) {
- const zone = (typeof Zone === 'undefined') ? null : Zone.current;
- const w = watch(effectFn, (watch) => {
- if (!this.all.has(watch)) {
- return;
- }
- this.queue.set(watch, zone);
- }, allowSignalWrites);
- this.all.add(w);
- // Effects start dirty.
- w.notify();
- let unregisterOnDestroy;
- const destroy = () => {
- w.cleanup();
- unregisterOnDestroy?.();
- this.all.delete(w);
- this.queue.delete(w);
- };
- unregisterOnDestroy = destroyRef?.onDestroy(destroy);
- return {
- destroy,
- };
- }
- flush() {
- if (this.queue.size === 0) {
- return;
- }
- for (const [watch, zone] of this.queue) {
- this.queue.delete(watch);
- if (zone) {
- zone.run(() => watch.run());
- }
- else {
- watch.run();
- }
- }
- }
- get isQueueEmpty() {
- return this.queue.size === 0;
- }
- /** @nocollapse */
- static { this.ɵprov = ɵɵdefineInjectable({
- token: EffectManager,
- providedIn: 'root',
- factory: () => new EffectManager(),
- }); }
- }
- /**
- * Create a global `Effect` for the given reactive function.
- *
- * @developerPreview
- */
- function effect(effectFn, options) {
- !options?.injector && assertInInjectionContext(effect);
- const injector = options?.injector ?? inject$1(Injector);
- const effectManager = injector.get(EffectManager);
- const destroyRef = options?.manualCleanup !== true ? injector.get(DestroyRef) : null;
- return effectManager.create(effectFn, destroyRef, !!options?.allowSignalWrites);
- }
- /**
- * Compute the static styling (class/style) from `TAttributes`.
- *
- * This function should be called during `firstCreatePass` only.
- *
- * @param tNode The `TNode` into which the styling information should be loaded.
- * @param attrs `TAttributes` containing the styling information.
- * @param writeToHost Where should the resulting static styles be written?
- * - `false` Write to `TNode.stylesWithoutHost` / `TNode.classesWithoutHost`
- * - `true` Write to `TNode.styles` / `TNode.classes`
- */
- function computeStaticStyling(tNode, attrs, writeToHost) {
- ngDevMode &&
- assertFirstCreatePass(getTView(), 'Expecting to be called in first template pass only');
- let styles = writeToHost ? tNode.styles : null;
- let classes = writeToHost ? tNode.classes : null;
- let mode = 0;
- if (attrs !== null) {
- for (let i = 0; i < attrs.length; i++) {
- const value = attrs[i];
- if (typeof value === 'number') {
- mode = value;
- }
- else if (mode == 1 /* AttributeMarker.Classes */) {
- classes = concatStringsWithSpace(classes, value);
- }
- else if (mode == 2 /* AttributeMarker.Styles */) {
- const style = value;
- const styleValue = attrs[++i];
- styles = concatStringsWithSpace(styles, style + ': ' + styleValue + ';');
- }
- }
- }
- writeToHost ? tNode.styles = styles : tNode.stylesWithoutHost = styles;
- writeToHost ? tNode.classes = classes : tNode.classesWithoutHost = classes;
- }
- function collectNativeNodes(tView, lView, tNode, result, isProjection = false) {
- while (tNode !== null) {
- ngDevMode &&
- assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */ | 16 /* TNodeType.Projection */ | 32 /* TNodeType.Icu */);
- const lNode = lView[tNode.index];
- if (lNode !== null) {
- result.push(unwrapRNode(lNode));
- }
- // A given lNode can represent either a native node or a LContainer (when it is a host of a
- // ViewContainerRef). When we find a LContainer we need to descend into it to collect root nodes
- // from the views in this container.
- if (isLContainer(lNode)) {
- collectNativeNodesInLContainer(lNode, result);
- }
- const tNodeType = tNode.type;
- if (tNodeType & 8 /* TNodeType.ElementContainer */) {
- collectNativeNodes(tView, lView, tNode.child, result);
- }
- else if (tNodeType & 32 /* TNodeType.Icu */) {
- const nextRNode = icuContainerIterate(tNode, lView);
- let rNode;
- while (rNode = nextRNode()) {
- result.push(rNode);
- }
- }
- else if (tNodeType & 16 /* TNodeType.Projection */) {
- const nodesInSlot = getProjectionNodes(lView, tNode);
- if (Array.isArray(nodesInSlot)) {
- result.push(...nodesInSlot);
- }
- else {
- const parentView = getLViewParent(lView[DECLARATION_COMPONENT_VIEW]);
- ngDevMode && assertParentView(parentView);
- collectNativeNodes(parentView[TVIEW], parentView, nodesInSlot, result, true);
- }
- }
- tNode = isProjection ? tNode.projectionNext : tNode.next;
- }
- return result;
- }
- /**
- * Collects all root nodes in all views in a given LContainer.
- */
- function collectNativeNodesInLContainer(lContainer, result) {
- for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
- const lViewInAContainer = lContainer[i];
- const lViewFirstChildTNode = lViewInAContainer[TVIEW].firstChild;
- if (lViewFirstChildTNode !== null) {
- collectNativeNodes(lViewInAContainer[TVIEW], lViewInAContainer, lViewFirstChildTNode, result);
- }
- }
- // When an LContainer is created, the anchor (comment) node is:
- // - (1) either reused in case of an ElementContainer (<ng-container>)
- // - (2) or a new comment node is created
- // In the first case, the anchor comment node would be added to the final
- // list by the code in the `collectNativeNodes` function
- // (see the `result.push(unwrapRNode(lNode))` line), but the second
- // case requires extra handling: the anchor node needs to be added to the
- // final list manually. See additional information in the `createAnchorNode`
- // function in the `view_container_ref.ts`.
- //
- // In the first case, the same reference would be stored in the `NATIVE`
- // and `HOST` slots in an LContainer. Otherwise, this is the second case and
- // we should add an element to the final list.
- if (lContainer[NATIVE] !== lContainer[HOST]) {
- result.push(lContainer[NATIVE]);
- }
- }
- function detectChangesInternal(tView, lView, context, notifyErrorHandler = true) {
- const environment = lView[ENVIRONMENT];
- const rendererFactory = environment.rendererFactory;
- const afterRenderEventManager = environment.afterRenderEventManager;
- // Check no changes mode is a dev only mode used to verify that bindings have not changed
- // since they were assigned. We do not want to invoke renderer factory functions in that mode
- // to avoid any possible side-effects.
- const checkNoChangesMode = !!ngDevMode && isInCheckNoChangesMode();
- if (!checkNoChangesMode) {
- rendererFactory.begin?.();
- afterRenderEventManager?.begin();
- }
- try {
- refreshView(tView, lView, tView.template, context);
- }
- catch (error) {
- if (notifyErrorHandler) {
- handleError(lView, error);
- }
- throw error;
- }
- finally {
- if (!checkNoChangesMode) {
- rendererFactory.end?.();
- // One final flush of the effects queue to catch any effects created in `ngAfterViewInit` or
- // other post-order hooks.
- environment.effectManager?.flush();
- // Invoke all callbacks registered via `after*Render`, if needed.
- afterRenderEventManager?.end();
- }
- }
- }
- function checkNoChangesInternal(tView, lView, context, notifyErrorHandler = true) {
- setIsInCheckNoChangesMode(true);
- try {
- detectChangesInternal(tView, lView, context, notifyErrorHandler);
- }
- finally {
- setIsInCheckNoChangesMode(false);
- }
- }
- /**
- * Synchronously perform change detection on a component (and possibly its sub-components).
- *
- * This function triggers change detection in a synchronous way on a component.
- *
- * @param component The component which the change detection should be performed on.
- */
- function detectChanges(component) {
- const view = getComponentViewByInstance(component);
- detectChangesInternal(view[TVIEW], view, component);
- }
- /**
- * Processes a view in update mode. This includes a number of steps in a specific order:
- * - executing a template function in update mode;
- * - executing hooks;
- * - refreshing queries;
- * - setting host bindings;
- * - refreshing child (embedded and component) views.
- */
- function refreshView(tView, lView, templateFn, context) {
- ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode');
- const flags = lView[FLAGS];
- if ((flags & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */)
- return;
- // Check no changes mode is a dev only mode used to verify that bindings have not changed
- // since they were assigned. We do not want to execute lifecycle hooks in that mode.
- const isInCheckNoChangesPass = ngDevMode && isInCheckNoChangesMode();
- !isInCheckNoChangesPass && lView[ENVIRONMENT].effectManager?.flush();
- enterView(lView);
- try {
- resetPreOrderHookFlags(lView);
- setBindingIndex(tView.bindingStartIndex);
- if (templateFn !== null) {
- executeTemplate(tView, lView, templateFn, 2 /* RenderFlags.Update */, context);
- }
- const hooksInitPhaseCompleted = (flags & 3 /* LViewFlags.InitPhaseStateMask */) === 3 /* InitPhaseState.InitPhaseCompleted */;
- // execute pre-order hooks (OnInit, OnChanges, DoCheck)
- // PERF WARNING: do NOT extract this to a separate function without running benchmarks
- if (!isInCheckNoChangesPass) {
- if (hooksInitPhaseCompleted) {
- const preOrderCheckHooks = tView.preOrderCheckHooks;
- if (preOrderCheckHooks !== null) {
- executeCheckHooks(lView, preOrderCheckHooks, null);
- }
- }
- else {
- const preOrderHooks = tView.preOrderHooks;
- if (preOrderHooks !== null) {
- executeInitAndCheckHooks(lView, preOrderHooks, 0 /* InitPhaseState.OnInitHooksToBeRun */, null);
- }
- incrementInitPhaseFlags(lView, 0 /* InitPhaseState.OnInitHooksToBeRun */);
- }
- }
- // First mark transplanted views that are declared in this lView as needing a refresh at their
- // insertion points. This is needed to avoid the situation where the template is defined in this
- // `LView` but its declaration appears after the insertion component.
- markTransplantedViewsForRefresh(lView);
- detectChangesInEmbeddedViews(lView, 2 /* ChangeDetectionMode.BugToForceRefreshAndIgnoreViewFlags */);
- // Content query results must be refreshed before content hooks are called.
- if (tView.contentQueries !== null) {
- refreshContentQueries(tView, lView);
- }
- // execute content hooks (AfterContentInit, AfterContentChecked)
- // PERF WARNING: do NOT extract this to a separate function without running benchmarks
- if (!isInCheckNoChangesPass) {
- if (hooksInitPhaseCompleted) {
- const contentCheckHooks = tView.contentCheckHooks;
- if (contentCheckHooks !== null) {
- executeCheckHooks(lView, contentCheckHooks);
- }
- }
- else {
- const contentHooks = tView.contentHooks;
- if (contentHooks !== null) {
- executeInitAndCheckHooks(lView, contentHooks, 1 /* InitPhaseState.AfterContentInitHooksToBeRun */);
- }
- incrementInitPhaseFlags(lView, 1 /* InitPhaseState.AfterContentInitHooksToBeRun */);
- }
- }
- processHostBindingOpCodes(tView, lView);
- // Refresh child component views.
- const components = tView.components;
- if (components !== null) {
- detectChangesInChildComponents(lView, components, 0 /* ChangeDetectionMode.Global */);
- }
- // View queries must execute after refreshing child components because a template in this view
- // could be inserted in a child component. If the view query executes before child component
- // refresh, the template might not yet be inserted.
- const viewQuery = tView.viewQuery;
- if (viewQuery !== null) {
- executeViewQueryFn(2 /* RenderFlags.Update */, viewQuery, context);
- }
- // execute view hooks (AfterViewInit, AfterViewChecked)
- // PERF WARNING: do NOT extract this to a separate function without running benchmarks
- if (!isInCheckNoChangesPass) {
- if (hooksInitPhaseCompleted) {
- const viewCheckHooks = tView.viewCheckHooks;
- if (viewCheckHooks !== null) {
- executeCheckHooks(lView, viewCheckHooks);
- }
- }
- else {
- const viewHooks = tView.viewHooks;
- if (viewHooks !== null) {
- executeInitAndCheckHooks(lView, viewHooks, 2 /* InitPhaseState.AfterViewInitHooksToBeRun */);
- }
- incrementInitPhaseFlags(lView, 2 /* InitPhaseState.AfterViewInitHooksToBeRun */);
- }
- }
- if (tView.firstUpdatePass === true) {
- // We need to make sure that we only flip the flag on successful `refreshView` only
- // Don't do this in `finally` block.
- // If we did this in `finally` block then an exception could block the execution of styling
- // instructions which in turn would be unable to insert themselves into the styling linked
- // list. The result of this would be that if the exception would not be throw on subsequent CD
- // the styling would be unable to process it data and reflect to the DOM.
- tView.firstUpdatePass = false;
- }
- // Do not reset the dirty state when running in check no changes mode. We don't want components
- // to behave differently depending on whether check no changes is enabled or not. For example:
- // Marking an OnPush component as dirty from within the `ngAfterViewInit` hook in order to
- // refresh a `NgClass` binding should work. If we would reset the dirty state in the check
- // no changes cycle, the component would be not be dirty for the next update pass. This would
- // be different in production mode where the component dirty state is not reset.
- if (!isInCheckNoChangesPass) {
- lView[FLAGS] &= ~(64 /* LViewFlags.Dirty */ | 8 /* LViewFlags.FirstLViewPass */);
- }
- clearViewRefreshFlag(lView);
- }
- finally {
- leaveView();
- }
- }
- /**
- * Goes over embedded views (ones created through ViewContainerRef APIs) and refreshes
- * them by executing an associated template function.
- */
- function detectChangesInEmbeddedViews(lView, mode) {
- for (let lContainer = getFirstLContainer(lView); lContainer !== null; lContainer = getNextLContainer(lContainer)) {
- for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
- const embeddedLView = lContainer[i];
- detectChangesInView(embeddedLView, mode);
- }
- }
- }
- /**
- * Mark transplanted views as needing to be refreshed at their insertion points.
- *
- * @param lView The `LView` that may have transplanted views.
- */
- function markTransplantedViewsForRefresh(lView) {
- for (let lContainer = getFirstLContainer(lView); lContainer !== null; lContainer = getNextLContainer(lContainer)) {
- if (!lContainer[HAS_TRANSPLANTED_VIEWS])
- continue;
- const movedViews = lContainer[MOVED_VIEWS];
- ngDevMode && assertDefined(movedViews, 'Transplanted View flags set but missing MOVED_VIEWS');
- for (let i = 0; i < movedViews.length; i++) {
- const movedLView = movedViews[i];
- const insertionLContainer = movedLView[PARENT];
- ngDevMode && assertLContainer(insertionLContainer);
- markViewForRefresh(movedLView);
- }
- }
- }
- /**
- * Detects changes in a component by entering the component view and processing its bindings,
- * queries, etc. if it is CheckAlways, OnPush and Dirty, etc.
- *
- * @param componentHostIdx Element index in LView[] (adjusted for HEADER_OFFSET)
- */
- function detectChangesInComponent(hostLView, componentHostIdx, mode) {
- ngDevMode && assertEqual(isCreationMode(hostLView), false, 'Should be run in update mode');
- const componentView = getComponentLViewByIndex(componentHostIdx, hostLView);
- detectChangesInView(componentView, mode);
- }
- /**
- * Visits a view as part of change detection traversal.
- *
- * - If the view is detached, no additional traversal happens.
- *
- * The view is refreshed if:
- * - If the view is CheckAlways or Dirty and ChangeDetectionMode is `Global`
- * - If the view has the `RefreshTransplantedView` flag
- *
- * The view is not refreshed, but descendants are traversed in `ChangeDetectionMode.Targeted` if the
- * view has a non-zero TRANSPLANTED_VIEWS_TO_REFRESH counter.
- *
- */
- function detectChangesInView(lView, mode) {
- if (!viewAttachedToChangeDetector(lView)) {
- return;
- }
- const tView = lView[TVIEW];
- const flags = lView[FLAGS];
- if ((flags & (16 /* LViewFlags.CheckAlways */ | 64 /* LViewFlags.Dirty */) &&
- mode === 0 /* ChangeDetectionMode.Global */) ||
- flags & 1024 /* LViewFlags.RefreshView */ ||
- mode === 2 /* ChangeDetectionMode.BugToForceRefreshAndIgnoreViewFlags */) {
- refreshView(tView, lView, tView.template, lView[CONTEXT]);
- }
- else if (lView[DESCENDANT_VIEWS_TO_REFRESH] > 0) {
- detectChangesInEmbeddedViews(lView, 1 /* ChangeDetectionMode.Targeted */);
- const components = tView.components;
- if (components !== null) {
- detectChangesInChildComponents(lView, components, 1 /* ChangeDetectionMode.Targeted */);
- }
- }
- }
- /** Refreshes child components in the current view (update mode). */
- function detectChangesInChildComponents(hostLView, components, mode) {
- for (let i = 0; i < components.length; i++) {
- detectChangesInComponent(hostLView, components[i], mode);
- }
- }
- class ViewRef {
- get rootNodes() {
- const lView = this._lView;
- const tView = lView[TVIEW];
- return collectNativeNodes(tView, lView, tView.firstChild, []);
- }
- constructor(
- /**
- * This represents `LView` associated with the component when ViewRef is a ChangeDetectorRef.
- *
- * When ViewRef is created for a dynamic component, this also represents the `LView` for the
- * component.
- *
- * For a "regular" ViewRef created for an embedded view, this is the `LView` for the embedded
- * view.
- *
- * @internal
- */
- _lView,
- /**
- * This represents the `LView` associated with the point where `ChangeDetectorRef` was
- * requested.
- *
- * This may be different from `_lView` if the `_cdRefInjectingView` is an embedded view.
- */
- _cdRefInjectingView) {
- this._lView = _lView;
- this._cdRefInjectingView = _cdRefInjectingView;
- this._appRef = null;
- this._attachedToViewContainer = false;
- }
- get context() {
- return this._lView[CONTEXT];
- }
- set context(value) {
- this._lView[CONTEXT] = value;
- }
- get destroyed() {
- return (this._lView[FLAGS] & 256 /* LViewFlags.Destroyed */) === 256 /* LViewFlags.Destroyed */;
- }
- destroy() {
- if (this._appRef) {
- this._appRef.detachView(this);
- }
- else if (this._attachedToViewContainer) {
- const parent = this._lView[PARENT];
- if (isLContainer(parent)) {
- const viewRefs = parent[VIEW_REFS];
- const index = viewRefs ? viewRefs.indexOf(this) : -1;
- if (index > -1) {
- ngDevMode &&
- assertEqual(index, parent.indexOf(this._lView) - CONTAINER_HEADER_OFFSET, 'An attached view should be in the same position within its container as its ViewRef in the VIEW_REFS array.');
- detachView(parent, index);
- removeFromArray(viewRefs, index);
- }
- }
- this._attachedToViewContainer = false;
- }
- destroyLView(this._lView[TVIEW], this._lView);
- }
- onDestroy(callback) {
- storeLViewOnDestroy(this._lView, callback);
- }
- /**
- * Marks a view and all of its ancestors dirty.
- *
- * This can be used to ensure an {@link ChangeDetectionStrategy#OnPush} component is
- * checked when it needs to be re-rendered but the two normal triggers haven't marked it
- * dirty (i.e. inputs haven't changed and events haven't fired in the view).
- *
- * <!-- TODO: Add a link to a chapter on OnPush components -->
- *
- * @usageNotes
- * ### Example
- *
- * ```typescript
- * @Component({
- * selector: 'app-root',
- * template: `Number of ticks: {{numberOfTicks}}`
- * changeDetection: ChangeDetectionStrategy.OnPush,
- * })
- * class AppComponent {
- * numberOfTicks = 0;
- *
- * constructor(private ref: ChangeDetectorRef) {
- * setInterval(() => {
- * this.numberOfTicks++;
- * // the following is required, otherwise the view will not be updated
- * this.ref.markForCheck();
- * }, 1000);
- * }
- * }
- * ```
- */
- markForCheck() {
- markViewDirty(this._cdRefInjectingView || this._lView);
- }
- /**
- * Detaches the view from the change detection tree.
- *
- * Detached views will not be checked during change detection runs until they are
- * re-attached, even if they are dirty. `detach` can be used in combination with
- * {@link ChangeDetectorRef#detectChanges} to implement local change
- * detection checks.
- *
- * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
- * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
- *
- * @usageNotes
- * ### Example
- *
- * The following example defines a component with a large list of readonly data.
- * Imagine the data changes constantly, many times per second. For performance reasons,
- * we want to check and update the list every five seconds. We can do that by detaching
- * the component's change detector and doing a local check every five seconds.
- *
- * ```typescript
- * class DataProvider {
- * // in a real application the returned data will be different every time
- * get data() {
- * return [1,2,3,4,5];
- * }
- * }
- *
- * @Component({
- * selector: 'giant-list',
- * template: `
- * <li *ngFor="let d of dataProvider.data">Data {{d}}</li>
- * `,
- * })
- * class GiantList {
- * constructor(private ref: ChangeDetectorRef, private dataProvider: DataProvider) {
- * ref.detach();
- * setInterval(() => {
- * this.ref.detectChanges();
- * }, 5000);
- * }
- * }
- *
- * @Component({
- * selector: 'app',
- * providers: [DataProvider],
- * template: `
- * <giant-list><giant-list>
- * `,
- * })
- * class App {
- * }
- * ```
- */
- detach() {
- this._lView[FLAGS] &= ~128 /* LViewFlags.Attached */;
- }
- /**
- * Re-attaches a view to the change detection tree.
- *
- * This can be used to re-attach views that were previously detached from the tree
- * using {@link ChangeDetectorRef#detach}. Views are attached to the tree by default.
- *
- * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
- *
- * @usageNotes
- * ### Example
- *
- * The following example creates a component displaying `live` data. The component will detach
- * its change detector from the main change detector tree when the component's live property
- * is set to false.
- *
- * ```typescript
- * class DataProvider {
- * data = 1;
- *
- * constructor() {
- * setInterval(() => {
- * this.data = this.data * 2;
- * }, 500);
- * }
- * }
- *
- * @Component({
- * selector: 'live-data',
- * inputs: ['live'],
- * template: 'Data: {{dataProvider.data}}'
- * })
- * class LiveData {
- * constructor(private ref: ChangeDetectorRef, private dataProvider: DataProvider) {}
- *
- * set live(value) {
- * if (value) {
- * this.ref.reattach();
- * } else {
- * this.ref.detach();
- * }
- * }
- * }
- *
- * @Component({
- * selector: 'app-root',
- * providers: [DataProvider],
- * template: `
- * Live Update: <input type="checkbox" [(ngModel)]="live">
- * <live-data [live]="live"><live-data>
- * `,
- * })
- * class AppComponent {
- * live = true;
- * }
- * ```
- */
- reattach() {
- this._lView[FLAGS] |= 128 /* LViewFlags.Attached */;
- }
- /**
- * Checks the view and its children.
- *
- * This can also be used in combination with {@link ChangeDetectorRef#detach} to implement
- * local change detection checks.
- *
- * <!-- TODO: Add a link to a chapter on detach/reattach/local digest -->
- * <!-- TODO: Add a live demo once ref.detectChanges is merged into master -->
- *
- * @usageNotes
- * ### Example
- *
- * The following example defines a component with a large list of readonly data.
- * Imagine, the data changes constantly, many times per second. For performance reasons,
- * we want to check and update the list every five seconds.
- *
- * We can do that by detaching the component's change detector and doing a local change detection
- * check every five seconds.
- *
- * See {@link ChangeDetectorRef#detach} for more information.
- */
- detectChanges() {
- detectChangesInternal(this._lView[TVIEW], this._lView, this.context);
- }
- /**
- * Checks the change detector and its children, and throws if any changes are detected.
- *
- * This is used in development mode to verify that running change detection doesn't
- * introduce other changes.
- */
- checkNoChanges() {
- if (ngDevMode) {
- checkNoChangesInternal(this._lView[TVIEW], this._lView, this.context);
- }
- }
- attachToViewContainerRef() {
- if (this._appRef) {
- throw new RuntimeError(902 /* RuntimeErrorCode.VIEW_ALREADY_ATTACHED */, ngDevMode && 'This view is already attached directly to the ApplicationRef!');
- }
- this._attachedToViewContainer = true;
- }
- detachFromAppRef() {
- this._appRef = null;
- detachViewFromDOM(this._lView[TVIEW], this._lView);
- }
- attachToAppRef(appRef) {
- if (this._attachedToViewContainer) {
- throw new RuntimeError(902 /* RuntimeErrorCode.VIEW_ALREADY_ATTACHED */, ngDevMode && 'This view is already attached to a ViewContainer!');
- }
- this._appRef = appRef;
- }
- }
- /** @internal */
- class RootViewRef extends ViewRef {
- constructor(_view) {
- super(_view);
- this._view = _view;
- }
- detectChanges() {
- const lView = this._view;
- const tView = lView[TVIEW];
- const context = lView[CONTEXT];
- detectChangesInternal(tView, lView, context, false);
- }
- checkNoChanges() {
- if (ngDevMode) {
- const lView = this._view;
- const tView = lView[TVIEW];
- const context = lView[CONTEXT];
- checkNoChangesInternal(tView, lView, context, false);
- }
- }
- get context() {
- return null;
- }
- }
- class ComponentFactoryResolver extends ComponentFactoryResolver$1 {
- /**
- * @param ngModule The NgModuleRef to which all resolved factories are bound.
- */
- constructor(ngModule) {
- super();
- this.ngModule = ngModule;
- }
- resolveComponentFactory(component) {
- ngDevMode && assertComponentType(component);
- const componentDef = getComponentDef$1(component);
- return new ComponentFactory(componentDef, this.ngModule);
- }
- }
- function toRefArray(map) {
- const array = [];
- for (let nonMinified in map) {
- if (map.hasOwnProperty(nonMinified)) {
- const minified = map[nonMinified];
- array.push({ propName: minified, templateName: nonMinified });
- }
- }
- return array;
- }
- function getNamespace(elementName) {
- const name = elementName.toLowerCase();
- return name === 'svg' ? SVG_NAMESPACE : (name === 'math' ? MATH_ML_NAMESPACE : null);
- }
- /**
- * Injector that looks up a value using a specific injector, before falling back to the module
- * injector. Used primarily when creating components or embedded views dynamically.
- */
- class ChainedInjector {
- constructor(injector, parentInjector) {
- this.injector = injector;
- this.parentInjector = parentInjector;
- }
- get(token, notFoundValue, flags) {
- flags = convertToBitFlags(flags);
- const value = this.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR, flags);
- if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
- notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
- // Return the value from the root element injector when
- // - it provides it
- // (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
- // - the module injector should not be checked
- // (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
- return value;
- }
- return this.parentInjector.get(token, notFoundValue, flags);
- }
- }
- /**
- * ComponentFactory interface implementation.
- */
- class ComponentFactory extends ComponentFactory$1 {
- get inputs() {
- const componentDef = this.componentDef;
- const inputTransforms = componentDef.inputTransforms;
- const refArray = toRefArray(componentDef.inputs);
- if (inputTransforms !== null) {
- for (const input of refArray) {
- if (inputTransforms.hasOwnProperty(input.propName)) {
- input.transform = inputTransforms[input.propName];
- }
- }
- }
- return refArray;
- }
- get outputs() {
- return toRefArray(this.componentDef.outputs);
- }
- /**
- * @param componentDef The component definition.
- * @param ngModule The NgModuleRef to which the factory is bound.
- */
- constructor(componentDef, ngModule) {
- super();
- this.componentDef = componentDef;
- this.ngModule = ngModule;
- this.componentType = componentDef.type;
- this.selector = stringifyCSSSelectorList(componentDef.selectors);
- this.ngContentSelectors =
- componentDef.ngContentSelectors ? componentDef.ngContentSelectors : [];
- this.isBoundToModule = !!ngModule;
- }
- create(injector, projectableNodes, rootSelectorOrNode, environmentInjector) {
- environmentInjector = environmentInjector || this.ngModule;
- let realEnvironmentInjector = environmentInjector instanceof EnvironmentInjector ?
- environmentInjector :
- environmentInjector?.injector;
- if (realEnvironmentInjector && this.componentDef.getStandaloneInjector !== null) {
- realEnvironmentInjector = this.componentDef.getStandaloneInjector(realEnvironmentInjector) ||
- realEnvironmentInjector;
- }
- const rootViewInjector = realEnvironmentInjector ? new ChainedInjector(injector, realEnvironmentInjector) : injector;
- const rendererFactory = rootViewInjector.get(RendererFactory2, null);
- if (rendererFactory === null) {
- throw new RuntimeError(407 /* RuntimeErrorCode.RENDERER_NOT_FOUND */, ngDevMode &&
- 'Angular was not able to inject a renderer (RendererFactory2). ' +
- 'Likely this is due to a broken DI hierarchy. ' +
- 'Make sure that any injector used to create this component has a correct parent.');
- }
- const sanitizer = rootViewInjector.get(Sanitizer, null);
- const effectManager = rootViewInjector.get(EffectManager, null);
- const afterRenderEventManager = rootViewInjector.get(AfterRenderEventManager, null);
- const environment = {
- rendererFactory,
- sanitizer,
- effectManager,
- afterRenderEventManager,
- };
- const hostRenderer = rendererFactory.createRenderer(null, this.componentDef);
- // Determine a tag name used for creating host elements when this component is created
- // dynamically. Default to 'div' if this component did not specify any tag name in its selector.
- const elementName = this.componentDef.selectors[0][0] || 'div';
- const hostRNode = rootSelectorOrNode ?
- locateHostElement(hostRenderer, rootSelectorOrNode, this.componentDef.encapsulation, rootViewInjector) :
- createElementNode(hostRenderer, elementName, getNamespace(elementName));
- // Signal components use the granular "RefreshView" for change detection
- const signalFlags = (4096 /* LViewFlags.SignalView */ | 512 /* LViewFlags.IsRoot */);
- // Non-signal components use the traditional "CheckAlways or OnPush/Dirty" change detection
- const nonSignalFlags = this.componentDef.onPush ? 64 /* LViewFlags.Dirty */ | 512 /* LViewFlags.IsRoot */ :
- 16 /* LViewFlags.CheckAlways */ | 512 /* LViewFlags.IsRoot */;
- const rootFlags = this.componentDef.signals ? signalFlags : nonSignalFlags;
- let hydrationInfo = null;
- if (hostRNode !== null) {
- hydrationInfo = retrieveHydrationInfo(hostRNode, rootViewInjector, true /* isRootView */);
- }
- // Create the root view. Uses empty TView and ContentTemplate.
- const rootTView = createTView(0 /* TViewType.Root */, null, null, 1, 0, null, null, null, null, null, null);
- const rootLView = createLView(null, rootTView, null, rootFlags, null, null, environment, hostRenderer, rootViewInjector, null, hydrationInfo);
- // rootView is the parent when bootstrapping
- // TODO(misko): it looks like we are entering view here but we don't really need to as
- // `renderView` does that. However as the code is written it is needed because
- // `createRootComponentView` and `createRootComponent` both read global state. Fixing those
- // issues would allow us to drop this.
- enterView(rootLView);
- let component;
- let tElementNode;
- try {
- const rootComponentDef = this.componentDef;
- let rootDirectives;
- let hostDirectiveDefs = null;
- if (rootComponentDef.findHostDirectiveDefs) {
- rootDirectives = [];
- hostDirectiveDefs = new Map();
- rootComponentDef.findHostDirectiveDefs(rootComponentDef, rootDirectives, hostDirectiveDefs);
- rootDirectives.push(rootComponentDef);
- }
- else {
- rootDirectives = [rootComponentDef];
- }
- const hostTNode = createRootComponentTNode(rootLView, hostRNode);
- const componentView = createRootComponentView(hostTNode, hostRNode, rootComponentDef, rootDirectives, rootLView, environment, hostRenderer);
- tElementNode = getTNode(rootTView, HEADER_OFFSET);
- // TODO(crisbeto): in practice `hostRNode` should always be defined, but there are some tests
- // where the renderer is mocked out and `undefined` is returned. We should update the tests so
- // that this check can be removed.
- if (hostRNode) {
- setRootNodeAttributes(hostRenderer, rootComponentDef, hostRNode, rootSelectorOrNode);
- }
- if (projectableNodes !== undefined) {
- projectNodes(tElementNode, this.ngContentSelectors, projectableNodes);
- }
- // TODO: should LifecycleHooksFeature and other host features be generated by the compiler and
- // executed here?
- // Angular 5 reference: https://stackblitz.com/edit/lifecycle-hooks-vcref
- component = createRootComponent(componentView, rootComponentDef, rootDirectives, hostDirectiveDefs, rootLView, [LifecycleHooksFeature]);
- renderView(rootTView, rootLView, null);
- }
- finally {
- leaveView();
- }
- return new ComponentRef(this.componentType, component, createElementRef(tElementNode, rootLView), rootLView, tElementNode);
- }
- }
- /**
- * Represents an instance of a Component created via a {@link ComponentFactory}.
- *
- * `ComponentRef` provides access to the Component Instance as well other objects related to this
- * Component Instance and allows you to destroy the Component Instance via the {@link #destroy}
- * method.
- *
- */
- class ComponentRef extends ComponentRef$1 {
- constructor(componentType, instance, location, _rootLView, _tNode) {
- super();
- this.location = location;
- this._rootLView = _rootLView;
- this._tNode = _tNode;
- this.previousInputValues = null;
- this.instance = instance;
- this.hostView = this.changeDetectorRef = new RootViewRef(_rootLView);
- this.componentType = componentType;
- }
- setInput(name, value) {
- const inputData = this._tNode.inputs;
- let dataValue;
- if (inputData !== null && (dataValue = inputData[name])) {
- this.previousInputValues ??= new Map();
- // Do not set the input if it is the same as the last value
- // This behavior matches `bindingUpdated` when binding inputs in templates.
- if (this.previousInputValues.has(name) &&
- Object.is(this.previousInputValues.get(name), value)) {
- return;
- }
- const lView = this._rootLView;
- setInputsForProperty(lView[TVIEW], lView, dataValue, name, value);
- this.previousInputValues.set(name, value);
- const childComponentLView = getComponentLViewByIndex(this._tNode.index, lView);
- markViewDirty(childComponentLView);
- }
- else {
- if (ngDevMode) {
- const cmpNameForError = stringifyForError(this.componentType);
- let message = `Can't set value of the '${name}' input on the '${cmpNameForError}' component. `;
- message += `Make sure that the '${name}' property is annotated with @Input() or a mapped @Input('${name}') exists.`;
- reportUnknownPropertyError(message);
- }
- }
- }
- get injector() {
- return new NodeInjector(this._tNode, this._rootLView);
- }
- destroy() {
- this.hostView.destroy();
- }
- onDestroy(callback) {
- this.hostView.onDestroy(callback);
- }
- }
- /** Creates a TNode that can be used to instantiate a root component. */
- function createRootComponentTNode(lView, rNode) {
- const tView = lView[TVIEW];
- const index = HEADER_OFFSET;
- ngDevMode && assertIndexInRange(lView, index);
- lView[index] = rNode;
- // '#host' is added here as we don't know the real host DOM name (we don't want to read it) and at
- // the same time we want to communicate the debug `TNode` that this is a special `TNode`
- // representing a host element.
- return getOrCreateTNode(tView, index, 2 /* TNodeType.Element */, '#host', null);
- }
- /**
- * Creates the root component view and the root component node.
- *
- * @param hostRNode Render host element.
- * @param rootComponentDef ComponentDef
- * @param rootView The parent view where the host node is stored
- * @param rendererFactory Factory to be used for creating child renderers.
- * @param hostRenderer The current renderer
- * @param sanitizer The sanitizer, if provided
- *
- * @returns Component view created
- */
- function createRootComponentView(tNode, hostRNode, rootComponentDef, rootDirectives, rootView, environment, hostRenderer) {
- const tView = rootView[TVIEW];
- applyRootComponentStyling(rootDirectives, tNode, hostRNode, hostRenderer);
- // Hydration info is on the host element and needs to be retrieved
- // and passed to the component LView.
- let hydrationInfo = null;
- if (hostRNode !== null) {
- hydrationInfo = retrieveHydrationInfo(hostRNode, rootView[INJECTOR$1]);
- }
- const viewRenderer = environment.rendererFactory.createRenderer(hostRNode, rootComponentDef);
- let lViewFlags = 16 /* LViewFlags.CheckAlways */;
- if (rootComponentDef.signals) {
- lViewFlags = 4096 /* LViewFlags.SignalView */;
- }
- else if (rootComponentDef.onPush) {
- lViewFlags = 64 /* LViewFlags.Dirty */;
- }
- const componentView = createLView(rootView, getOrCreateComponentTView(rootComponentDef), null, lViewFlags, rootView[tNode.index], tNode, environment, viewRenderer, null, null, hydrationInfo);
- if (tView.firstCreatePass) {
- markAsComponentHost(tView, tNode, rootDirectives.length - 1);
- }
- addToViewTree(rootView, componentView);
- // Store component view at node index, with node as the HOST
- return rootView[tNode.index] = componentView;
- }
- /** Sets up the styling information on a root component. */
- function applyRootComponentStyling(rootDirectives, tNode, rNode, hostRenderer) {
- for (const def of rootDirectives) {
- tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
- }
- if (tNode.mergedAttrs !== null) {
- computeStaticStyling(tNode, tNode.mergedAttrs, true);
- if (rNode !== null) {
- setupStaticAttributes(hostRenderer, rNode, tNode);
- }
- }
- }
- /**
- * Creates a root component and sets it up with features and host bindings.Shared by
- * renderComponent() and ViewContainerRef.createComponent().
- */
- function createRootComponent(componentView, rootComponentDef, rootDirectives, hostDirectiveDefs, rootLView, hostFeatures) {
- const rootTNode = getCurrentTNode();
- ngDevMode && assertDefined(rootTNode, 'tNode should have been already created');
- const tView = rootLView[TVIEW];
- const native = getNativeByTNode(rootTNode, rootLView);
- initializeDirectives(tView, rootLView, rootTNode, rootDirectives, null, hostDirectiveDefs);
- for (let i = 0; i < rootDirectives.length; i++) {
- const directiveIndex = rootTNode.directiveStart + i;
- const directiveInstance = getNodeInjectable(rootLView, tView, directiveIndex, rootTNode);
- attachPatchData(directiveInstance, rootLView);
- }
- invokeDirectivesHostBindings(tView, rootLView, rootTNode);
- if (native) {
- attachPatchData(native, rootLView);
- }
- // We're guaranteed for the `componentOffset` to be positive here
- // since a root component always matches a component def.
- ngDevMode &&
- assertGreaterThan(rootTNode.componentOffset, -1, 'componentOffset must be great than -1');
- const component = getNodeInjectable(rootLView, tView, rootTNode.directiveStart + rootTNode.componentOffset, rootTNode);
- componentView[CONTEXT] = rootLView[CONTEXT] = component;
- if (hostFeatures !== null) {
- for (const feature of hostFeatures) {
- feature(component, rootComponentDef);
- }
- }
- // We want to generate an empty QueryList for root content queries for backwards
- // compatibility with ViewEngine.
- executeContentQueries(tView, rootTNode, componentView);
- return component;
- }
- /** Sets the static attributes on a root component. */
- function setRootNodeAttributes(hostRenderer, componentDef, hostRNode, rootSelectorOrNode) {
- if (rootSelectorOrNode) {
- setUpAttributes(hostRenderer, hostRNode, ['ng-version', VERSION.full]);
- }
- else {
- // If host element is created as a part of this function call (i.e. `rootSelectorOrNode`
- // is not defined), also apply attributes and classes extracted from component selector.
- // Extract attributes and classes from the first selector only to match VE behavior.
- const { attrs, classes } = extractAttrsAndClassesFromSelector(componentDef.selectors[0]);
- if (attrs) {
- setUpAttributes(hostRenderer, hostRNode, attrs);
- }
- if (classes && classes.length > 0) {
- writeDirectClass(hostRenderer, hostRNode, classes.join(' '));
- }
- }
- }
- /** Projects the `projectableNodes` that were specified when creating a root component. */
- function projectNodes(tNode, ngContentSelectors, projectableNodes) {
- const projection = tNode.projection = [];
- for (let i = 0; i < ngContentSelectors.length; i++) {
- const nodesforSlot = projectableNodes[i];
- // Projectable nodes can be passed as array of arrays or an array of iterables (ngUpgrade
- // case). Here we do normalize passed data structure to be an array of arrays to avoid
- // complex checks down the line.
- // We also normalize the length of the passed in projectable nodes (to match the number of
- // <ng-container> slots defined by a component).
- projection.push(nodesforSlot != null ? Array.from(nodesforSlot) : null);
- }
- }
- /**
- * Used to enable lifecycle hooks on the root component.
- *
- * Include this feature when calling `renderComponent` if the root component
- * you are rendering has lifecycle hooks defined. Otherwise, the hooks won't
- * be called properly.
- *
- * Example:
- *
- * ```
- * renderComponent(AppComponent, {hostFeatures: [LifecycleHooksFeature]});
- * ```
- */
- function LifecycleHooksFeature() {
- const tNode = getCurrentTNode();
- ngDevMode && assertDefined(tNode, 'TNode is required');
- registerPostOrderHooks(getLView()[TVIEW], tNode);
- }
- function getSuperType(type) {
- return Object.getPrototypeOf(type.prototype).constructor;
- }
- /**
- * Merges the definition from a super class to a sub class.
- * @param definition The definition that is a SubClass of another directive of component
- *
- * @codeGenApi
- */
- function ɵɵInheritDefinitionFeature(definition) {
- let superType = getSuperType(definition.type);
- let shouldInheritFields = true;
- const inheritanceChain = [definition];
- while (superType) {
- let superDef = undefined;
- if (isComponentDef(definition)) {
- // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance.
- superDef = superType.ɵcmp || superType.ɵdir;
- }
- else {
- if (superType.ɵcmp) {
- throw new RuntimeError(903 /* RuntimeErrorCode.INVALID_INHERITANCE */, ngDevMode &&
- `Directives cannot inherit Components. Directive ${stringifyForError(definition.type)} is attempting to extend component ${stringifyForError(superType)}`);
- }
- // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance.
- superDef = superType.ɵdir;
- }
- if (superDef) {
- if (shouldInheritFields) {
- inheritanceChain.push(superDef);
- // Some fields in the definition may be empty, if there were no values to put in them that
- // would've justified object creation. Unwrap them if necessary.
- const writeableDef = definition;
- writeableDef.inputs = maybeUnwrapEmpty(definition.inputs);
- writeableDef.inputTransforms = maybeUnwrapEmpty(definition.inputTransforms);
- writeableDef.declaredInputs = maybeUnwrapEmpty(definition.declaredInputs);
- writeableDef.outputs = maybeUnwrapEmpty(definition.outputs);
- // Merge hostBindings
- const superHostBindings = superDef.hostBindings;
- superHostBindings && inheritHostBindings(definition, superHostBindings);
- // Merge queries
- const superViewQuery = superDef.viewQuery;
- const superContentQueries = superDef.contentQueries;
- superViewQuery && inheritViewQuery(definition, superViewQuery);
- superContentQueries && inheritContentQueries(definition, superContentQueries);
- // Merge inputs and outputs
- fillProperties(definition.inputs, superDef.inputs);
- fillProperties(definition.declaredInputs, superDef.declaredInputs);
- fillProperties(definition.outputs, superDef.outputs);
- if (superDef.inputTransforms !== null) {
- if (writeableDef.inputTransforms === null) {
- writeableDef.inputTransforms = {};
- }
- fillProperties(writeableDef.inputTransforms, superDef.inputTransforms);
- }
- // Merge animations metadata.
- // If `superDef` is a Component, the `data` field is present (defaults to an empty object).
- if (isComponentDef(superDef) && superDef.data.animation) {
- // If super def is a Component, the `definition` is also a Component, since Directives can
- // not inherit Components (we throw an error above and cannot reach this code).
- const defData = definition.data;
- defData.animation = (defData.animation || []).concat(superDef.data.animation);
- }
- }
- // Run parent features
- const features = superDef.features;
- if (features) {
- for (let i = 0; i < features.length; i++) {
- const feature = features[i];
- if (feature && feature.ngInherit) {
- feature(definition);
- }
- // If `InheritDefinitionFeature` is a part of the current `superDef`, it means that this
- // def already has all the necessary information inherited from its super class(es), so we
- // can stop merging fields from super classes. However we need to iterate through the
- // prototype chain to look for classes that might contain other "features" (like
- // NgOnChanges), which we should invoke for the original `definition`. We set the
- // `shouldInheritFields` flag to indicate that, essentially skipping fields inheritance
- // logic and only invoking functions from the "features" list.
- if (feature === ɵɵInheritDefinitionFeature) {
- shouldInheritFields = false;
- }
- }
- }
- }
- superType = Object.getPrototypeOf(superType);
- }
- mergeHostAttrsAcrossInheritance(inheritanceChain);
- }
- /**
- * Merge the `hostAttrs` and `hostVars` from the inherited parent to the base class.
- *
- * @param inheritanceChain A list of `WritableDefs` starting at the top most type and listing
- * sub-types in order. For each type take the `hostAttrs` and `hostVars` and merge it with the child
- * type.
- */
- function mergeHostAttrsAcrossInheritance(inheritanceChain) {
- let hostVars = 0;
- let hostAttrs = null;
- // We process the inheritance order from the base to the leaves here.
- for (let i = inheritanceChain.length - 1; i >= 0; i--) {
- const def = inheritanceChain[i];
- // For each `hostVars`, we need to add the superclass amount.
- def.hostVars = (hostVars += def.hostVars);
- // for each `hostAttrs` we need to merge it with superclass.
- def.hostAttrs =
- mergeHostAttrs(def.hostAttrs, hostAttrs = mergeHostAttrs(hostAttrs, def.hostAttrs));
- }
- }
- function maybeUnwrapEmpty(value) {
- if (value === EMPTY_OBJ) {
- return {};
- }
- else if (value === EMPTY_ARRAY) {
- return [];
- }
- else {
- return value;
- }
- }
- function inheritViewQuery(definition, superViewQuery) {
- const prevViewQuery = definition.viewQuery;
- if (prevViewQuery) {
- definition.viewQuery = (rf, ctx) => {
- superViewQuery(rf, ctx);
- prevViewQuery(rf, ctx);
- };
- }
- else {
- definition.viewQuery = superViewQuery;
- }
- }
- function inheritContentQueries(definition, superContentQueries) {
- const prevContentQueries = definition.contentQueries;
- if (prevContentQueries) {
- definition.contentQueries = (rf, ctx, directiveIndex) => {
- superContentQueries(rf, ctx, directiveIndex);
- prevContentQueries(rf, ctx, directiveIndex);
- };
- }
- else {
- definition.contentQueries = superContentQueries;
- }
- }
- function inheritHostBindings(definition, superHostBindings) {
- const prevHostBindings = definition.hostBindings;
- if (prevHostBindings) {
- definition.hostBindings = (rf, ctx) => {
- superHostBindings(rf, ctx);
- prevHostBindings(rf, ctx);
- };
- }
- else {
- definition.hostBindings = superHostBindings;
- }
- }
- /**
- * Fields which exist on either directive or component definitions, and need to be copied from
- * parent to child classes by the `ɵɵCopyDefinitionFeature`.
- */
- const COPY_DIRECTIVE_FIELDS = [
- // The child class should use the providers of its parent.
- 'providersResolver',
- // Not listed here are any fields which are handled by the `ɵɵInheritDefinitionFeature`, such
- // as inputs, outputs, and host binding functions.
- ];
- /**
- * Fields which exist only on component definitions, and need to be copied from parent to child
- * classes by the `ɵɵCopyDefinitionFeature`.
- *
- * The type here allows any field of `ComponentDef` which is not also a property of `DirectiveDef`,
- * since those should go in `COPY_DIRECTIVE_FIELDS` above.
- */
- const COPY_COMPONENT_FIELDS = [
- // The child class should use the template function of its parent, including all template
- // semantics.
- 'template',
- 'decls',
- 'consts',
- 'vars',
- 'onPush',
- 'ngContentSelectors',
- // The child class should use the CSS styles of its parent, including all styling semantics.
- 'styles',
- 'encapsulation',
- // The child class should be checked by the runtime in the same way as its parent.
- 'schemas',
- ];
- /**
- * Copies the fields not handled by the `ɵɵInheritDefinitionFeature` from the supertype of a
- * definition.
- *
- * This exists primarily to support ngcc migration of an existing View Engine pattern, where an
- * entire decorator is inherited from a parent to a child class. When ngcc detects this case, it
- * generates a skeleton definition on the child class, and applies this feature.
- *
- * The `ɵɵCopyDefinitionFeature` then copies any needed fields from the parent class' definition,
- * including things like the component template function.
- *
- * @param definition The definition of a child class which inherits from a parent class with its
- * own definition.
- *
- * @codeGenApi
- */
- function ɵɵCopyDefinitionFeature(definition) {
- let superType = getSuperType(definition.type);
- let superDef = undefined;
- if (isComponentDef(definition)) {
- // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance.
- superDef = superType.ɵcmp;
- }
- else {
- // Don't use getComponentDef/getDirectiveDef. This logic relies on inheritance.
- superDef = superType.ɵdir;
- }
- // Needed because `definition` fields are readonly.
- const defAny = definition;
- // Copy over any fields that apply to either directives or components.
- for (const field of COPY_DIRECTIVE_FIELDS) {
- defAny[field] = superDef[field];
- }
- if (isComponentDef(superDef)) {
- // Copy over any component-specific fields.
- for (const field of COPY_COMPONENT_FIELDS) {
- defAny[field] = superDef[field];
- }
- }
- }
- /**
- * This feature adds the host directives behavior to a directive definition by patching a
- * function onto it. The expectation is that the runtime will invoke the function during
- * directive matching.
- *
- * For example:
- * ```ts
- * class ComponentWithHostDirective {
- * static ɵcmp = defineComponent({
- * type: ComponentWithHostDirective,
- * features: [ɵɵHostDirectivesFeature([
- * SimpleHostDirective,
- * {directive: AdvancedHostDirective, inputs: ['foo: alias'], outputs: ['bar']},
- * ])]
- * });
- * }
- * ```
- *
- * @codeGenApi
- */
- function ɵɵHostDirectivesFeature(rawHostDirectives) {
- return (definition) => {
- definition.findHostDirectiveDefs = findHostDirectiveDefs;
- definition.hostDirectives =
- (Array.isArray(rawHostDirectives) ? rawHostDirectives : rawHostDirectives()).map(dir => {
- return typeof dir === 'function' ?
- { directive: resolveForwardRef(dir), inputs: EMPTY_OBJ, outputs: EMPTY_OBJ } :
- {
- directive: resolveForwardRef(dir.directive),
- inputs: bindingArrayToMap(dir.inputs),
- outputs: bindingArrayToMap(dir.outputs)
- };
- });
- };
- }
- function findHostDirectiveDefs(currentDef, matchedDefs, hostDirectiveDefs) {
- if (currentDef.hostDirectives !== null) {
- for (const hostDirectiveConfig of currentDef.hostDirectives) {
- const hostDirectiveDef = getDirectiveDef(hostDirectiveConfig.directive);
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
- validateHostDirective(hostDirectiveConfig, hostDirectiveDef, matchedDefs);
- }
- // We need to patch the `declaredInputs` so that
- // `ngOnChanges` can map the properties correctly.
- patchDeclaredInputs(hostDirectiveDef.declaredInputs, hostDirectiveConfig.inputs);
- // Host directives execute before the host so that its host bindings can be overwritten.
- findHostDirectiveDefs(hostDirectiveDef, matchedDefs, hostDirectiveDefs);
- hostDirectiveDefs.set(hostDirectiveDef, hostDirectiveConfig);
- matchedDefs.push(hostDirectiveDef);
- }
- }
- }
- /**
- * Converts an array in the form of `['publicName', 'alias', 'otherPublicName', 'otherAlias']` into
- * a map in the form of `{publicName: 'alias', otherPublicName: 'otherAlias'}`.
- */
- function bindingArrayToMap(bindings) {
- if (bindings === undefined || bindings.length === 0) {
- return EMPTY_OBJ;
- }
- const result = {};
- for (let i = 0; i < bindings.length; i += 2) {
- result[bindings[i]] = bindings[i + 1];
- }
- return result;
- }
- /**
- * `ngOnChanges` has some leftover legacy ViewEngine behavior where the keys inside the
- * `SimpleChanges` event refer to the *declared* name of the input, not its public name or its
- * minified name. E.g. in `@Input('alias') foo: string`, the name in the `SimpleChanges` object
- * will always be `foo`, and not `alias` or the minified name of `foo` in apps using property
- * minification.
- *
- * This is achieved through the `DirectiveDef.declaredInputs` map that is constructed when the
- * definition is declared. When a property is written to the directive instance, the
- * `NgOnChangesFeature` will try to remap the property name being written to using the
- * `declaredInputs`.
- *
- * Since the host directive input remapping happens during directive matching, `declaredInputs`
- * won't contain the new alias that the input is available under. This function addresses the
- * issue by patching the host directive aliases to the `declaredInputs`. There is *not* a risk of
- * this patching accidentally introducing new inputs to the host directive, because `declaredInputs`
- * is used *only* by the `NgOnChangesFeature` when determining what name is used in the
- * `SimpleChanges` object which won't be reached if an input doesn't exist.
- */
- function patchDeclaredInputs(declaredInputs, exposedInputs) {
- for (const publicName in exposedInputs) {
- if (exposedInputs.hasOwnProperty(publicName)) {
- const remappedPublicName = exposedInputs[publicName];
- const privateName = declaredInputs[publicName];
- // We *technically* shouldn't be able to hit this case because we can't have multiple
- // inputs on the same property and we have validations against conflicting aliases in
- // `validateMappings`. If we somehow did, it would lead to `ngOnChanges` being invoked
- // with the wrong name so we have a non-user-friendly assertion here just in case.
- if ((typeof ngDevMode === 'undefined' || ngDevMode) &&
- declaredInputs.hasOwnProperty(remappedPublicName)) {
- assertEqual(declaredInputs[remappedPublicName], declaredInputs[publicName], `Conflicting host directive input alias ${publicName}.`);
- }
- declaredInputs[remappedPublicName] = privateName;
- }
- }
- }
- /**
- * Verifies that the host directive has been configured correctly.
- * @param hostDirectiveConfig Host directive configuration object.
- * @param directiveDef Directive definition of the host directive.
- * @param matchedDefs Directives that have been matched so far.
- */
- function validateHostDirective(hostDirectiveConfig, directiveDef, matchedDefs) {
- const type = hostDirectiveConfig.directive;
- if (directiveDef === null) {
- if (getComponentDef$1(type) !== null) {
- throw new RuntimeError(310 /* RuntimeErrorCode.HOST_DIRECTIVE_COMPONENT */, `Host directive ${type.name} cannot be a component.`);
- }
- throw new RuntimeError(307 /* RuntimeErrorCode.HOST_DIRECTIVE_UNRESOLVABLE */, `Could not resolve metadata for host directive ${type.name}. ` +
- `Make sure that the ${type.name} class is annotated with an @Directive decorator.`);
- }
- if (!directiveDef.standalone) {
- throw new RuntimeError(308 /* RuntimeErrorCode.HOST_DIRECTIVE_NOT_STANDALONE */, `Host directive ${directiveDef.type.name} must be standalone.`);
- }
- if (matchedDefs.indexOf(directiveDef) > -1) {
- throw new RuntimeError(309 /* RuntimeErrorCode.DUPLICATE_DIRECTITVE */, `Directive ${directiveDef.type.name} matches multiple times on the same element. ` +
- `Directives can only match an element once.`);
- }
- validateMappings('input', directiveDef, hostDirectiveConfig.inputs);
- validateMappings('output', directiveDef, hostDirectiveConfig.outputs);
- }
- /**
- * Checks that the host directive inputs/outputs configuration is valid.
- * @param bindingType Kind of binding that is being validated. Used in the error message.
- * @param def Definition of the host directive that is being validated against.
- * @param hostDirectiveBindings Host directive mapping object that shold be validated.
- */
- function validateMappings(bindingType, def, hostDirectiveBindings) {
- const className = def.type.name;
- const bindings = bindingType === 'input' ? def.inputs : def.outputs;
- for (const publicName in hostDirectiveBindings) {
- if (hostDirectiveBindings.hasOwnProperty(publicName)) {
- if (!bindings.hasOwnProperty(publicName)) {
- throw new RuntimeError(311 /* RuntimeErrorCode.HOST_DIRECTIVE_UNDEFINED_BINDING */, `Directive ${className} does not have an ${bindingType} with a public name of ${publicName}.`);
- }
- const remappedPublicName = hostDirectiveBindings[publicName];
- if (bindings.hasOwnProperty(remappedPublicName) && remappedPublicName !== publicName &&
- bindings[remappedPublicName] !== publicName) {
- throw new RuntimeError(312 /* RuntimeErrorCode.HOST_DIRECTIVE_CONFLICTING_ALIAS */, `Cannot alias ${bindingType} ${publicName} of host directive ${className} to ${remappedPublicName}, because it already has a different ${bindingType} with the same public name.`);
- }
- }
- }
- }
- /**
- * Decorates the directive definition with support for input transform functions.
- *
- * If the directive uses inheritance, the feature should be included before the
- * `InheritDefinitionFeature` to ensure that the `inputTransforms` field is populated.
- *
- * @codeGenApi
- */
- function ɵɵInputTransformsFeature(definition) {
- const inputs = definition.inputConfig;
- const inputTransforms = {};
- for (const minifiedKey in inputs) {
- if (inputs.hasOwnProperty(minifiedKey)) {
- // Note: the private names are used for the keys, rather than the public ones, because public
- // names can be re-aliased in host directives which would invalidate the lookup.
- const value = inputs[minifiedKey];
- if (Array.isArray(value) && value[2]) {
- inputTransforms[minifiedKey] = value[2];
- }
- }
- }
- definition.inputTransforms =
- inputTransforms;
- }
- function isIterable(obj) {
- return obj !== null && typeof obj === 'object' && obj[Symbol.iterator] !== undefined;
- }
- function isListLikeIterable(obj) {
- if (!isJsObject(obj))
- return false;
- return Array.isArray(obj) ||
- (!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
- Symbol.iterator in obj); // JS Iterable have a Symbol.iterator prop
- }
- function areIterablesEqual(a, b, comparator) {
- const iterator1 = a[Symbol.iterator]();
- const iterator2 = b[Symbol.iterator]();
- while (true) {
- const item1 = iterator1.next();
- const item2 = iterator2.next();
- if (item1.done && item2.done)
- return true;
- if (item1.done || item2.done)
- return false;
- if (!comparator(item1.value, item2.value))
- return false;
- }
- }
- function iterateListLike(obj, fn) {
- if (Array.isArray(obj)) {
- for (let i = 0; i < obj.length; i++) {
- fn(obj[i]);
- }
- }
- else {
- const iterator = obj[Symbol.iterator]();
- let item;
- while (!((item = iterator.next()).done)) {
- fn(item.value);
- }
- }
- }
- function isJsObject(o) {
- return o !== null && (typeof o === 'function' || typeof o === 'object');
- }
- function devModeEqual(a, b) {
- const isListLikeIterableA = isListLikeIterable(a);
- const isListLikeIterableB = isListLikeIterable(b);
- if (isListLikeIterableA && isListLikeIterableB) {
- return areIterablesEqual(a, b, devModeEqual);
- }
- else {
- const isAObject = a && (typeof a === 'object' || typeof a === 'function');
- const isBObject = b && (typeof b === 'object' || typeof b === 'function');
- if (!isListLikeIterableA && isAObject && !isListLikeIterableB && isBObject) {
- return true;
- }
- else {
- return Object.is(a, b);
- }
- }
- }
- // TODO(misko): consider inlining
- /** Updates binding and returns the value. */
- function updateBinding(lView, bindingIndex, value) {
- return lView[bindingIndex] = value;
- }
- /** Gets the current binding value. */
- function getBinding(lView, bindingIndex) {
- ngDevMode && assertIndexInRange(lView, bindingIndex);
- ngDevMode &&
- assertNotSame(lView[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
- return lView[bindingIndex];
- }
- /**
- * Updates binding if changed, then returns whether it was updated.
- *
- * This function also checks the `CheckNoChangesMode` and throws if changes are made.
- * Some changes (Objects/iterables) during `CheckNoChangesMode` are exempt to comply with VE
- * behavior.
- *
- * @param lView current `LView`
- * @param bindingIndex The binding in the `LView` to check
- * @param value New value to check against `lView[bindingIndex]`
- * @returns `true` if the bindings has changed. (Throws if binding has changed during
- * `CheckNoChangesMode`)
- */
- function bindingUpdated(lView, bindingIndex, value) {
- ngDevMode && assertNotSame(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
- ngDevMode &&
- assertLessThan(bindingIndex, lView.length, `Slot should have been initialized to NO_CHANGE`);
- const oldValue = lView[bindingIndex];
- if (Object.is(oldValue, value)) {
- return false;
- }
- else {
- if (ngDevMode && isInCheckNoChangesMode()) {
- // View engine didn't report undefined values as changed on the first checkNoChanges pass
- // (before the change detection was run).
- const oldValueToCompare = oldValue !== NO_CHANGE ? oldValue : undefined;
- if (!devModeEqual(oldValueToCompare, value)) {
- const details = getExpressionChangedErrorDetails(lView, bindingIndex, oldValueToCompare, value);
- throwErrorIfNoChangesMode(oldValue === NO_CHANGE, details.oldValue, details.newValue, details.propName, lView);
- }
- // There was a change, but the `devModeEqual` decided that the change is exempt from an error.
- // For this reason we exit as if no change. The early exit is needed to prevent the changed
- // value to be written into `LView` (If we would write the new value that we would not see it
- // as change on next CD.)
- return false;
- }
- lView[bindingIndex] = value;
- return true;
- }
- }
- /** Updates 2 bindings if changed, then returns whether either was updated. */
- function bindingUpdated2(lView, bindingIndex, exp1, exp2) {
- const different = bindingUpdated(lView, bindingIndex, exp1);
- return bindingUpdated(lView, bindingIndex + 1, exp2) || different;
- }
- /** Updates 3 bindings if changed, then returns whether any was updated. */
- function bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3) {
- const different = bindingUpdated2(lView, bindingIndex, exp1, exp2);
- return bindingUpdated(lView, bindingIndex + 2, exp3) || different;
- }
- /** Updates 4 bindings if changed, then returns whether any was updated. */
- function bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4) {
- const different = bindingUpdated2(lView, bindingIndex, exp1, exp2);
- return bindingUpdated2(lView, bindingIndex + 2, exp3, exp4) || different;
- }
- /**
- * Updates the value of or removes a bound attribute on an Element.
- *
- * Used in the case of `[attr.title]="value"`
- *
- * @param name name The name of the attribute.
- * @param value value The attribute is removed when value is `null` or `undefined`.
- * Otherwise the attribute value is set to the stringified value.
- * @param sanitizer An optional function used to sanitize the value.
- * @param namespace Optional namespace to use when setting the attribute.
- *
- * @codeGenApi
- */
- function ɵɵattribute(name, value, sanitizer, namespace) {
- const lView = getLView();
- const bindingIndex = nextBindingIndex();
- if (bindingUpdated(lView, bindingIndex, value)) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, name, value, sanitizer, namespace);
- ngDevMode && storePropertyBindingMetadata(tView.data, tNode, 'attr.' + name, bindingIndex);
- }
- return ɵɵattribute;
- }
- /**
- * Create interpolation bindings with a variable number of expressions.
- *
- * If there are 1 to 8 expressions `interpolation1()` to `interpolation8()` should be used instead.
- * Those are faster because there is no need to create an array of expressions and iterate over it.
- *
- * `values`:
- * - has static text at even indexes,
- * - has evaluated expressions at odd indexes.
- *
- * Returns the concatenated string when any of the arguments changes, `NO_CHANGE` otherwise.
- */
- function interpolationV(lView, values) {
- ngDevMode && assertLessThan(2, values.length, 'should have at least 3 values');
- ngDevMode && assertEqual(values.length % 2, 1, 'should have an odd number of values');
- let isBindingUpdated = false;
- let bindingIndex = getBindingIndex();
- for (let i = 1; i < values.length; i += 2) {
- // Check if bindings (odd indexes) have changed
- isBindingUpdated = bindingUpdated(lView, bindingIndex++, values[i]) || isBindingUpdated;
- }
- setBindingIndex(bindingIndex);
- if (!isBindingUpdated) {
- return NO_CHANGE;
- }
- // Build the updated content
- let content = values[0];
- for (let i = 1; i < values.length; i += 2) {
- content += renderStringify(values[i]) + values[i + 1];
- }
- return content;
- }
- /**
- * Creates an interpolation binding with 1 expression.
- *
- * @param prefix static value used for concatenation only.
- * @param v0 value checked for change.
- * @param suffix static value used for concatenation only.
- */
- function interpolation1(lView, prefix, v0, suffix) {
- const different = bindingUpdated(lView, nextBindingIndex(), v0);
- return different ? prefix + renderStringify(v0) + suffix : NO_CHANGE;
- }
- /**
- * Creates an interpolation binding with 2 expressions.
- */
- function interpolation2(lView, prefix, v0, i0, v1, suffix) {
- const bindingIndex = getBindingIndex();
- const different = bindingUpdated2(lView, bindingIndex, v0, v1);
- incrementBindingIndex(2);
- return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + suffix : NO_CHANGE;
- }
- /**
- * Creates an interpolation binding with 3 expressions.
- */
- function interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix) {
- const bindingIndex = getBindingIndex();
- const different = bindingUpdated3(lView, bindingIndex, v0, v1, v2);
- incrementBindingIndex(3);
- return different ?
- prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + suffix :
- NO_CHANGE;
- }
- /**
- * Create an interpolation binding with 4 expressions.
- */
- function interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix) {
- const bindingIndex = getBindingIndex();
- const different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
- incrementBindingIndex(4);
- return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 +
- renderStringify(v2) + i2 + renderStringify(v3) + suffix :
- NO_CHANGE;
- }
- /**
- * Creates an interpolation binding with 5 expressions.
- */
- function interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) {
- const bindingIndex = getBindingIndex();
- let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
- different = bindingUpdated(lView, bindingIndex + 4, v4) || different;
- incrementBindingIndex(5);
- return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 +
- renderStringify(v2) + i2 + renderStringify(v3) + i3 + renderStringify(v4) + suffix :
- NO_CHANGE;
- }
- /**
- * Creates an interpolation binding with 6 expressions.
- */
- function interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) {
- const bindingIndex = getBindingIndex();
- let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
- different = bindingUpdated2(lView, bindingIndex + 4, v4, v5) || different;
- incrementBindingIndex(6);
- return different ?
- prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 + renderStringify(v2) + i2 +
- renderStringify(v3) + i3 + renderStringify(v4) + i4 + renderStringify(v5) + suffix :
- NO_CHANGE;
- }
- /**
- * Creates an interpolation binding with 7 expressions.
- */
- function interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) {
- const bindingIndex = getBindingIndex();
- let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
- different = bindingUpdated3(lView, bindingIndex + 4, v4, v5, v6) || different;
- incrementBindingIndex(7);
- return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 +
- renderStringify(v2) + i2 + renderStringify(v3) + i3 + renderStringify(v4) + i4 +
- renderStringify(v5) + i5 + renderStringify(v6) + suffix :
- NO_CHANGE;
- }
- /**
- * Creates an interpolation binding with 8 expressions.
- */
- function interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) {
- const bindingIndex = getBindingIndex();
- let different = bindingUpdated4(lView, bindingIndex, v0, v1, v2, v3);
- different = bindingUpdated4(lView, bindingIndex + 4, v4, v5, v6, v7) || different;
- incrementBindingIndex(8);
- return different ? prefix + renderStringify(v0) + i0 + renderStringify(v1) + i1 +
- renderStringify(v2) + i2 + renderStringify(v3) + i3 + renderStringify(v4) + i4 +
- renderStringify(v5) + i5 + renderStringify(v6) + i6 + renderStringify(v7) + suffix :
- NO_CHANGE;
- }
- /**
- *
- * Update an interpolated attribute on an element with single bound value surrounded by text.
- *
- * Used when the value passed to a property has 1 interpolated value in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate1('title', 'prefix', v0, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate1(attrName, prefix, v0, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 1, prefix, suffix);
- }
- return ɵɵattributeInterpolate1;
- }
- /**
- *
- * Update an interpolated attribute on an element with 2 bound values surrounded by text.
- *
- * Used when the value passed to a property has 2 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate2('title', 'prefix', v0, '-', v1, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate2(attrName, prefix, v0, i0, v1, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 2, prefix, i0, suffix);
- }
- return ɵɵattributeInterpolate2;
- }
- /**
- *
- * Update an interpolated attribute on an element with 3 bound values surrounded by text.
- *
- * Used when the value passed to a property has 3 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}-{{v2}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate3(
- * 'title', 'prefix', v0, '-', v1, '-', v2, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate3(attrName, prefix, v0, i0, v1, i1, v2, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 3, prefix, i0, i1, suffix);
- }
- return ɵɵattributeInterpolate3;
- }
- /**
- *
- * Update an interpolated attribute on an element with 4 bound values surrounded by text.
- *
- * Used when the value passed to a property has 4 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate4(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate4(attrName, prefix, v0, i0, v1, i1, v2, i2, v3, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 4, prefix, i0, i1, i2, suffix);
- }
- return ɵɵattributeInterpolate4;
- }
- /**
- *
- * Update an interpolated attribute on an element with 5 bound values surrounded by text.
- *
- * Used when the value passed to a property has 5 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate5(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate5(attrName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 5, prefix, i0, i1, i2, i3, suffix);
- }
- return ɵɵattributeInterpolate5;
- }
- /**
- *
- * Update an interpolated attribute on an element with 6 bound values surrounded by text.
- *
- * Used when the value passed to a property has 6 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate6(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate6(attrName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 6, prefix, i0, i1, i2, i3, i4, suffix);
- }
- return ɵɵattributeInterpolate6;
- }
- /**
- *
- * Update an interpolated attribute on an element with 7 bound values surrounded by text.
- *
- * Used when the value passed to a property has 7 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate7(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate7(attrName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 7, prefix, i0, i1, i2, i3, i4, i5, suffix);
- }
- return ɵɵattributeInterpolate7;
- }
- /**
- *
- * Update an interpolated attribute on an element with 8 bound values surrounded by text.
- *
- * Used when the value passed to a property has 8 interpolated values in it:
- *
- * ```html
- * <div attr.title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolate8(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix');
- * ```
- *
- * @param attrName The name of the attribute to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param i6 Static value used for concatenation only.
- * @param v7 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolate8(attrName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix, sanitizer, namespace) {
- const lView = getLView();
- const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolatedValue, sanitizer, namespace);
- ngDevMode &&
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - 8, prefix, i0, i1, i2, i3, i4, i5, i6, suffix);
- }
- return ɵɵattributeInterpolate8;
- }
- /**
- * Update an interpolated attribute on an element with 9 or more bound values surrounded by text.
- *
- * Used when the number of interpolated values exceeds 8.
- *
- * ```html
- * <div
- * title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵattributeInterpolateV(
- * 'title', ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9,
- * 'suffix']);
- * ```
- *
- * @param attrName The name of the attribute to update.
- * @param values The collection of values and the strings in-between those values, beginning with
- * a string prefix and ending with a string suffix.
- * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵattributeInterpolateV(attrName, values, sanitizer, namespace) {
- const lView = getLView();
- const interpolated = interpolationV(lView, values);
- if (interpolated !== NO_CHANGE) {
- const tNode = getSelectedTNode();
- elementAttributeInternal(tNode, lView, attrName, interpolated, sanitizer, namespace);
- if (ngDevMode) {
- const interpolationInBetween = [values[0]]; // prefix
- for (let i = 2; i < values.length; i += 2) {
- interpolationInBetween.push(values[i]);
- }
- storePropertyBindingMetadata(getTView().data, tNode, 'attr.' + attrName, getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween);
- }
- }
- return ɵɵattributeInterpolateV;
- }
- const AT_THIS_LOCATION = '<-- AT THIS LOCATION';
- /**
- * Retrieves a user friendly string for a given TNodeType for use in
- * friendly error messages
- *
- * @param tNodeType
- * @returns
- */
- function getFriendlyStringFromTNodeType(tNodeType) {
- switch (tNodeType) {
- case 4 /* TNodeType.Container */:
- return 'view container';
- case 2 /* TNodeType.Element */:
- return 'element';
- case 8 /* TNodeType.ElementContainer */:
- return 'ng-container';
- case 32 /* TNodeType.Icu */:
- return 'icu';
- case 64 /* TNodeType.Placeholder */:
- return 'i18n';
- case 16 /* TNodeType.Projection */:
- return 'projection';
- case 1 /* TNodeType.Text */:
- return 'text';
- default:
- // This should not happen as we cover all possible TNode types above.
- return '<unknown>';
- }
- }
- /**
- * Validates that provided nodes match during the hydration process.
- */
- function validateMatchingNode(node, nodeType, tagName, lView, tNode, isViewContainerAnchor = false) {
- if (!node ||
- (node.nodeType !== nodeType ||
- (node.nodeType === Node.ELEMENT_NODE &&
- node.tagName.toLowerCase() !== tagName?.toLowerCase()))) {
- const expectedNode = shortRNodeDescription(nodeType, tagName, null);
- let header = `During hydration Angular expected ${expectedNode} but `;
- const hostComponentDef = getDeclarationComponentDef(lView);
- const componentClassName = hostComponentDef?.type?.name;
- const expected = `Angular expected this DOM:\n\n${describeExpectedDom(lView, tNode, isViewContainerAnchor)}\n\n`;
- let actual = '';
- if (!node) {
- // No node found during hydration.
- header += `the node was not found.\n\n`;
- }
- else {
- const actualNode = shortRNodeDescription(node.nodeType, node.tagName ?? null, node.textContent ?? null);
- header += `found ${actualNode}.\n\n`;
- actual = `Actual DOM is:\n\n${describeDomFromNode(node)}\n\n`;
- }
- const footer = getHydrationErrorFooter(componentClassName);
- const message = header + expected + actual + getHydrationAttributeNote() + footer;
- throw new RuntimeError(-500 /* RuntimeErrorCode.HYDRATION_NODE_MISMATCH */, message);
- }
- }
- /**
- * Validates that a given node has sibling nodes
- */
- function validateSiblingNodeExists(node) {
- validateNodeExists(node);
- if (!node.nextSibling) {
- const header = 'During hydration Angular expected more sibling nodes to be present.\n\n';
- const actual = `Actual DOM is:\n\n${describeDomFromNode(node)}\n\n`;
- const footer = getHydrationErrorFooter();
- const message = header + actual + footer;
- throw new RuntimeError(-501 /* RuntimeErrorCode.HYDRATION_MISSING_SIBLINGS */, message);
- }
- }
- /**
- * Validates that a node exists or throws
- */
- function validateNodeExists(node, lView = null, tNode = null) {
- if (!node) {
- const header = 'During hydration, Angular expected an element to be present at this location.\n\n';
- let expected = '';
- let footer = '';
- if (lView !== null && tNode !== null) {
- expected = `${describeExpectedDom(lView, tNode, false)}\n\n`;
- footer = getHydrationErrorFooter();
- }
- throw new RuntimeError(-502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + expected + footer);
- }
- }
- /**
- * Builds the hydration error message when a node is not found
- *
- * @param lView the LView where the node exists
- * @param tNode the TNode
- */
- function nodeNotFoundError(lView, tNode) {
- const header = 'During serialization, Angular was unable to find an element in the DOM:\n\n';
- const expected = `${describeExpectedDom(lView, tNode, false)}\n\n`;
- const footer = getHydrationErrorFooter();
- throw new RuntimeError(-502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + expected + footer);
- }
- /**
- * Builds a hydration error message when a node is not found at a path location
- *
- * @param host the Host Node
- * @param path the path to the node
- */
- function nodeNotFoundAtPathError(host, path) {
- const header = `During hydration Angular was unable to locate a node ` +
- `using the "${path}" path, starting from the ${describeRNode(host)} node.\n\n`;
- const footer = getHydrationErrorFooter();
- throw new RuntimeError(-502 /* RuntimeErrorCode.HYDRATION_MISSING_NODE */, header + footer);
- }
- /**
- * Builds the hydration error message in the case that dom nodes are created outside of
- * the Angular context and are being used as projected nodes
- *
- * @param lView the LView
- * @param tNode the TNode
- * @returns an error
- */
- function unsupportedProjectionOfDomNodes(rNode) {
- const header = 'During serialization, Angular detected DOM nodes ' +
- 'that were created outside of Angular context and provided as projectable nodes ' +
- '(likely via `ViewContainerRef.createComponent` or `createComponent` APIs). ' +
- 'Hydration is not supported for such cases, consider refactoring the code to avoid ' +
- 'this pattern or using `ngSkipHydration` on the host element of the component.\n\n';
- const actual = `${describeDomFromNode(rNode)}\n\n`;
- const message = header + actual + getHydrationAttributeNote();
- return new RuntimeError(-503 /* RuntimeErrorCode.UNSUPPORTED_PROJECTION_DOM_NODES */, message);
- }
- /**
- * Builds the hydration error message in the case that ngSkipHydration was used on a
- * node that is not a component host element or host binding
- *
- * @param rNode the HTML Element
- * @returns an error
- */
- function invalidSkipHydrationHost(rNode) {
- const header = 'The `ngSkipHydration` flag is applied on a node ' +
- 'that doesn\'t act as a component host. Hydration can be ' +
- 'skipped only on per-component basis.\n\n';
- const actual = `${describeDomFromNode(rNode)}\n\n`;
- const footer = 'Please move the `ngSkipHydration` attribute to the component host element.\n\n';
- const message = header + actual + footer;
- return new RuntimeError(-504 /* RuntimeErrorCode.INVALID_SKIP_HYDRATION_HOST */, message);
- }
- // Stringification methods
- /**
- * Stringifies a given TNode's attributes
- *
- * @param tNode a provided TNode
- * @returns string
- */
- function stringifyTNodeAttrs(tNode) {
- const results = [];
- if (tNode.attrs) {
- for (let i = 0; i < tNode.attrs.length;) {
- const attrName = tNode.attrs[i++];
- // Once we reach the first flag, we know that the list of
- // attributes is over.
- if (typeof attrName == 'number') {
- break;
- }
- const attrValue = tNode.attrs[i++];
- results.push(`${attrName}="${shorten(attrValue)}"`);
- }
- }
- return results.join(' ');
- }
- /**
- * The list of internal attributes that should be filtered out while
- * producing an error message.
- */
- const internalAttrs = new Set(['ngh', 'ng-version', 'ng-server-context']);
- /**
- * Stringifies an HTML Element's attributes
- *
- * @param rNode an HTML Element
- * @returns string
- */
- function stringifyRNodeAttrs(rNode) {
- const results = [];
- for (let i = 0; i < rNode.attributes.length; i++) {
- const attr = rNode.attributes[i];
- if (internalAttrs.has(attr.name))
- continue;
- results.push(`${attr.name}="${shorten(attr.value)}"`);
- }
- return results.join(' ');
- }
- // Methods for Describing the DOM
- /**
- * Converts a tNode to a helpful readable string value for use in error messages
- *
- * @param tNode a given TNode
- * @param innerContent the content of the node
- * @returns string
- */
- function describeTNode(tNode, innerContent = '…') {
- switch (tNode.type) {
- case 1 /* TNodeType.Text */:
- const content = tNode.value ? `(${tNode.value})` : '';
- return `#text${content}`;
- case 2 /* TNodeType.Element */:
- const attrs = stringifyTNodeAttrs(tNode);
- const tag = tNode.value.toLowerCase();
- return `<${tag}${attrs ? ' ' + attrs : ''}>${innerContent}</${tag}>`;
- case 8 /* TNodeType.ElementContainer */:
- return '<!-- ng-container -->';
- case 4 /* TNodeType.Container */:
- return '<!-- container -->';
- default:
- const typeAsString = getFriendlyStringFromTNodeType(tNode.type);
- return `#node(${typeAsString})`;
- }
- }
- /**
- * Converts an RNode to a helpful readable string value for use in error messages
- *
- * @param rNode a given RNode
- * @param innerContent the content of the node
- * @returns string
- */
- function describeRNode(rNode, innerContent = '…') {
- const node = rNode;
- switch (node.nodeType) {
- case Node.ELEMENT_NODE:
- const tag = node.tagName.toLowerCase();
- const attrs = stringifyRNodeAttrs(node);
- return `<${tag}${attrs ? ' ' + attrs : ''}>${innerContent}</${tag}>`;
- case Node.TEXT_NODE:
- const content = node.textContent ? shorten(node.textContent) : '';
- return `#text${content ? `(${content})` : ''}`;
- case Node.COMMENT_NODE:
- return `<!-- ${shorten(node.textContent ?? '')} -->`;
- default:
- return `#node(${node.nodeType})`;
- }
- }
- /**
- * Builds the string containing the expected DOM present given the LView and TNode
- * values for a readable error message
- *
- * @param lView the lView containing the DOM
- * @param tNode the tNode
- * @param isViewContainerAnchor boolean
- * @returns string
- */
- function describeExpectedDom(lView, tNode, isViewContainerAnchor) {
- const spacer = ' ';
- let content = '';
- if (tNode.prev) {
- content += spacer + '…\n';
- content += spacer + describeTNode(tNode.prev) + '\n';
- }
- else if (tNode.type && tNode.type & 12 /* TNodeType.AnyContainer */) {
- content += spacer + '…\n';
- }
- if (isViewContainerAnchor) {
- content += spacer + describeTNode(tNode) + '\n';
- content += spacer + `<!-- container --> ${AT_THIS_LOCATION}\n`;
- }
- else {
- content += spacer + describeTNode(tNode) + ` ${AT_THIS_LOCATION}\n`;
- }
- content += spacer + '…\n';
- const parentRNode = tNode.type ? getParentRElement(lView[TVIEW], tNode, lView) : null;
- if (parentRNode) {
- content = describeRNode(parentRNode, '\n' + content);
- }
- return content;
- }
- /**
- * Builds the string containing the DOM present around a given RNode for a
- * readable error message
- *
- * @param node the RNode
- * @returns string
- */
- function describeDomFromNode(node) {
- const spacer = ' ';
- let content = '';
- const currentNode = node;
- if (currentNode.previousSibling) {
- content += spacer + '…\n';
- content += spacer + describeRNode(currentNode.previousSibling) + '\n';
- }
- content += spacer + describeRNode(currentNode) + ` ${AT_THIS_LOCATION}\n`;
- if (node.nextSibling) {
- content += spacer + '…\n';
- }
- if (node.parentNode) {
- content = describeRNode(currentNode.parentNode, '\n' + content);
- }
- return content;
- }
- /**
- * Shortens the description of a given RNode by its type for readability
- *
- * @param nodeType the type of node
- * @param tagName the node tag name
- * @param textContent the text content in the node
- * @returns string
- */
- function shortRNodeDescription(nodeType, tagName, textContent) {
- switch (nodeType) {
- case Node.ELEMENT_NODE:
- return `<${tagName.toLowerCase()}>`;
- case Node.TEXT_NODE:
- const content = textContent ? ` (with the "${shorten(textContent)}" content)` : '';
- return `a text node${content}`;
- case Node.COMMENT_NODE:
- return 'a comment node';
- default:
- return `#node(nodeType=${nodeType})`;
- }
- }
- /**
- * Builds the footer hydration error message
- *
- * @param componentClassName the name of the component class
- * @returns string
- */
- function getHydrationErrorFooter(componentClassName) {
- const componentInfo = componentClassName ? `the "${componentClassName}"` : 'corresponding';
- return `To fix this problem:\n` +
- ` * check ${componentInfo} component for hydration-related issues\n` +
- ` * check to see if your template has valid HTML structure\n` +
- ` * or skip hydration by adding the \`ngSkipHydration\` attribute ` +
- `to its host node in a template\n\n`;
- }
- /**
- * An attribute related note for hydration errors
- */
- function getHydrationAttributeNote() {
- return 'Note: attributes are only displayed to better represent the DOM' +
- ' but have no effect on hydration mismatches.\n\n';
- }
- // Node string utility functions
- /**
- * Strips all newlines out of a given string
- *
- * @param input a string to be cleared of new line characters
- * @returns
- */
- function stripNewlines(input) {
- return input.replace(/\s+/gm, '');
- }
- /**
- * Reduces a string down to a maximum length of characters with ellipsis for readability
- *
- * @param input a string input
- * @param maxLength a maximum length in characters
- * @returns string
- */
- function shorten(input, maxLength = 50) {
- if (!input) {
- return '';
- }
- input = stripNewlines(input);
- return input.length > maxLength ? `${input.substring(0, maxLength - 1)}…` : input;
- }
- /**
- * Regexp that extracts a reference node information from the compressed node location.
- * The reference node is represented as either:
- * - a number which points to an LView slot
- * - the `b` char which indicates that the lookup should start from the `document.body`
- * - the `h` char to start lookup from the component host node (`lView[HOST]`)
- */
- const REF_EXTRACTOR_REGEXP = new RegExp(`^(\\d+)*(${REFERENCE_NODE_BODY}|${REFERENCE_NODE_HOST})*(.*)`);
- /**
- * Helper function that takes a reference node location and a set of navigation steps
- * (from the reference node) to a target node and outputs a string that represents
- * a location.
- *
- * For example, given: referenceNode = 'b' (body) and path = ['firstChild', 'firstChild',
- * 'nextSibling'], the function returns: `bf2n`.
- */
- function compressNodeLocation(referenceNode, path) {
- const result = [referenceNode];
- for (const segment of path) {
- const lastIdx = result.length - 1;
- if (lastIdx > 0 && result[lastIdx - 1] === segment) {
- // An empty string in a count slot represents 1 occurrence of an instruction.
- const value = (result[lastIdx] || 1);
- result[lastIdx] = value + 1;
- }
- else {
- // Adding a new segment to the path.
- // Using an empty string in a counter field to avoid encoding `1`s
- // into the path, since they are implicit (e.g. `f1n1` vs `fn`), so
- // it's enough to have a single char in this case.
- result.push(segment, '');
- }
- }
- return result.join('');
- }
- /**
- * Helper function that reverts the `compressNodeLocation` and transforms a given
- * string into an array where at 0th position there is a reference node info and
- * after that it contains information (in pairs) about a navigation step and the
- * number of repetitions.
- *
- * For example, the path like 'bf2n' will be transformed to:
- * ['b', 'firstChild', 2, 'nextSibling', 1].
- *
- * This information is later consumed by the code that navigates the DOM to find
- * a given node by its location.
- */
- function decompressNodeLocation(path) {
- const matches = path.match(REF_EXTRACTOR_REGEXP);
- const [_, refNodeId, refNodeName, rest] = matches;
- // If a reference node is represented by an index, transform it to a number.
- const ref = refNodeId ? parseInt(refNodeId, 10) : refNodeName;
- const steps = [];
- // Match all segments in a path.
- for (const [_, step, count] of rest.matchAll(/(f|n)(\d*)/g)) {
- const repeat = parseInt(count, 10) || 1;
- steps.push(step, repeat);
- }
- return [ref, ...steps];
- }
- /** Whether current TNode is a first node in an <ng-container>. */
- function isFirstElementInNgContainer(tNode) {
- return !tNode.prev && tNode.parent?.type === 8 /* TNodeType.ElementContainer */;
- }
- /** Returns an instruction index (subtracting HEADER_OFFSET). */
- function getNoOffsetIndex(tNode) {
- return tNode.index - HEADER_OFFSET;
- }
- /**
- * Locate a node in DOM tree that corresponds to a given TNode.
- *
- * @param hydrationInfo The hydration annotation data
- * @param tView the current tView
- * @param lView the current lView
- * @param tNode the current tNode
- * @returns an RNode that represents a given tNode
- */
- function locateNextRNode(hydrationInfo, tView, lView, tNode) {
- let native = null;
- const noOffsetIndex = getNoOffsetIndex(tNode);
- const nodes = hydrationInfo.data[NODES];
- if (nodes?.[noOffsetIndex]) {
- // We know the exact location of the node.
- native = locateRNodeByPath(nodes[noOffsetIndex], lView);
- }
- else if (tView.firstChild === tNode) {
- // We create a first node in this view, so we use a reference
- // to the first child in this DOM segment.
- native = hydrationInfo.firstChild;
- }
- else {
- // Locate a node based on a previous sibling or a parent node.
- const previousTNodeParent = tNode.prev === null;
- const previousTNode = (tNode.prev ?? tNode.parent);
- ngDevMode &&
- assertDefined(previousTNode, 'Unexpected state: current TNode does not have a connection ' +
- 'to the previous node or a parent node.');
- if (isFirstElementInNgContainer(tNode)) {
- const noOffsetParentIndex = getNoOffsetIndex(tNode.parent);
- native = getSegmentHead(hydrationInfo, noOffsetParentIndex);
- }
- else {
- let previousRElement = getNativeByTNode(previousTNode, lView);
- if (previousTNodeParent) {
- native = previousRElement.firstChild;
- }
- else {
- // If the previous node is an element, but it also has container info,
- // this means that we are processing a node like `<div #vcrTarget>`, which is
- // represented in the DOM as `<div></div>...<!--container-->`.
- // In this case, there are nodes *after* this element and we need to skip
- // all of them to reach an element that we are looking for.
- const noOffsetPrevSiblingIndex = getNoOffsetIndex(previousTNode);
- const segmentHead = getSegmentHead(hydrationInfo, noOffsetPrevSiblingIndex);
- if (previousTNode.type === 2 /* TNodeType.Element */ && segmentHead) {
- const numRootNodesToSkip = calcSerializedContainerSize(hydrationInfo, noOffsetPrevSiblingIndex);
- // `+1` stands for an anchor comment node after all the views in this container.
- const nodesToSkip = numRootNodesToSkip + 1;
- // First node after this segment.
- native = siblingAfter(nodesToSkip, segmentHead);
- }
- else {
- native = previousRElement.nextSibling;
- }
- }
- }
- }
- return native;
- }
- /**
- * Skips over a specified number of nodes and returns the next sibling node after that.
- */
- function siblingAfter(skip, from) {
- let currentNode = from;
- for (let i = 0; i < skip; i++) {
- ngDevMode && validateSiblingNodeExists(currentNode);
- currentNode = currentNode.nextSibling;
- }
- return currentNode;
- }
- /**
- * Helper function to produce a string representation of the navigation steps
- * (in terms of `nextSibling` and `firstChild` navigations). Used in error
- * messages in dev mode.
- */
- function stringifyNavigationInstructions(instructions) {
- const container = [];
- for (let i = 0; i < instructions.length; i += 2) {
- const step = instructions[i];
- const repeat = instructions[i + 1];
- for (let r = 0; r < repeat; r++) {
- container.push(step === NodeNavigationStep.FirstChild ? 'firstChild' : 'nextSibling');
- }
- }
- return container.join('.');
- }
- /**
- * Helper function that navigates from a starting point node (the `from` node)
- * using provided set of navigation instructions (within `path` argument).
- */
- function navigateToNode(from, instructions) {
- let node = from;
- for (let i = 0; i < instructions.length; i += 2) {
- const step = instructions[i];
- const repeat = instructions[i + 1];
- for (let r = 0; r < repeat; r++) {
- if (ngDevMode && !node) {
- throw nodeNotFoundAtPathError(from, stringifyNavigationInstructions(instructions));
- }
- switch (step) {
- case NodeNavigationStep.FirstChild:
- node = node.firstChild;
- break;
- case NodeNavigationStep.NextSibling:
- node = node.nextSibling;
- break;
- }
- }
- }
- if (ngDevMode && !node) {
- throw nodeNotFoundAtPathError(from, stringifyNavigationInstructions(instructions));
- }
- return node;
- }
- /**
- * Locates an RNode given a set of navigation instructions (which also contains
- * a starting point node info).
- */
- function locateRNodeByPath(path, lView) {
- const [referenceNode, ...navigationInstructions] = decompressNodeLocation(path);
- let ref;
- if (referenceNode === REFERENCE_NODE_HOST) {
- ref = lView[DECLARATION_COMPONENT_VIEW][HOST];
- }
- else if (referenceNode === REFERENCE_NODE_BODY) {
- ref = ɵɵresolveBody(lView[DECLARATION_COMPONENT_VIEW][HOST]);
- }
- else {
- const parentElementId = Number(referenceNode);
- ref = unwrapRNode(lView[parentElementId + HEADER_OFFSET]);
- }
- return navigateToNode(ref, navigationInstructions);
- }
- /**
- * Generate a list of DOM navigation operations to get from node `start` to node `finish`.
- *
- * Note: assumes that node `start` occurs before node `finish` in an in-order traversal of the DOM
- * tree. That is, we should be able to get from `start` to `finish` purely by using `.firstChild`
- * and `.nextSibling` operations.
- */
- function navigateBetween(start, finish) {
- if (start === finish) {
- return [];
- }
- else if (start.parentElement == null || finish.parentElement == null) {
- return null;
- }
- else if (start.parentElement === finish.parentElement) {
- return navigateBetweenSiblings(start, finish);
- }
- else {
- // `finish` is a child of its parent, so the parent will always have a child.
- const parent = finish.parentElement;
- const parentPath = navigateBetween(start, parent);
- const childPath = navigateBetween(parent.firstChild, finish);
- if (!parentPath || !childPath)
- return null;
- return [
- // First navigate to `finish`'s parent
- ...parentPath,
- // Then to its first child.
- NodeNavigationStep.FirstChild,
- // And finally from that node to `finish` (maybe a no-op if we're already there).
- ...childPath,
- ];
- }
- }
- /**
- * Calculates a path between 2 sibling nodes (generates a number of `NextSibling` navigations).
- * Returns `null` if no such path exists between the given nodes.
- */
- function navigateBetweenSiblings(start, finish) {
- const nav = [];
- let node = null;
- for (node = start; node != null && node !== finish; node = node.nextSibling) {
- nav.push(NodeNavigationStep.NextSibling);
- }
- // If the `node` becomes `null` or `undefined` at the end, that means that we
- // didn't find the `end` node, thus return `null` (which would trigger serialization
- // error to be produced).
- return node == null ? null : nav;
- }
- /**
- * Calculates a path between 2 nodes in terms of `nextSibling` and `firstChild`
- * navigations:
- * - the `from` node is a known node, used as an starting point for the lookup
- * (the `fromNodeName` argument is a string representation of the node).
- * - the `to` node is a node that the runtime logic would be looking up,
- * using the path generated by this function.
- */
- function calcPathBetween(from, to, fromNodeName) {
- const path = navigateBetween(from, to);
- return path === null ? null : compressNodeLocation(fromNodeName, path);
- }
- /**
- * Invoked at serialization time (on the server) when a set of navigation
- * instructions needs to be generated for a TNode.
- */
- function calcPathForNode(tNode, lView) {
- const parentTNode = tNode.parent;
- let parentIndex;
- let parentRNode;
- let referenceNodeName;
- if (parentTNode === null || !(parentTNode.type & 3 /* TNodeType.AnyRNode */)) {
- // If there is no parent TNode or a parent TNode does not represent an RNode
- // (i.e. not a DOM node), use component host element as a reference node.
- parentIndex = referenceNodeName = REFERENCE_NODE_HOST;
- parentRNode = lView[DECLARATION_COMPONENT_VIEW][HOST];
- }
- else {
- // Use parent TNode as a reference node.
- parentIndex = parentTNode.index;
- parentRNode = unwrapRNode(lView[parentIndex]);
- referenceNodeName = renderStringify(parentIndex - HEADER_OFFSET);
- }
- let rNode = unwrapRNode(lView[tNode.index]);
- if (tNode.type & 12 /* TNodeType.AnyContainer */) {
- // For <ng-container> nodes, instead of serializing a reference
- // to the anchor comment node, serialize a location of the first
- // DOM element. Paired with the container size (serialized as a part
- // of `ngh.containers`), it should give enough information for runtime
- // to hydrate nodes in this container.
- const firstRNode = getFirstNativeNode(lView, tNode);
- // If container is not empty, use a reference to the first element,
- // otherwise, rNode would point to an anchor comment node.
- if (firstRNode) {
- rNode = firstRNode;
- }
- }
- let path = calcPathBetween(parentRNode, rNode, referenceNodeName);
- if (path === null && parentRNode !== rNode) {
- // Searching for a path between elements within a host node failed.
- // Trying to find a path to an element starting from the `document.body` instead.
- //
- // Important note: this type of reference is relatively unstable, since Angular
- // may not be able to control parts of the page that the runtime logic navigates
- // through. This is mostly needed to cover "portals" use-case (like menus, dialog boxes,
- // etc), where nodes are content-projected (including direct DOM manipulations) outside
- // of the host node. The better solution is to provide APIs to work with "portals",
- // at which point this code path would not be needed.
- const body = parentRNode.ownerDocument.body;
- path = calcPathBetween(body, rNode, REFERENCE_NODE_BODY);
- if (path === null) {
- // If the path is still empty, it's likely that this node is detached and
- // won't be found during hydration.
- throw nodeNotFoundError(lView, tNode);
- }
- }
- return path;
- }
- function templateFirstCreatePass(index, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) {
- ngDevMode && assertFirstCreatePass(tView);
- ngDevMode && ngDevMode.firstCreatePass++;
- const tViewConsts = tView.consts;
- // TODO(pk): refactor getOrCreateTNode to have the "create" only version
- const tNode = getOrCreateTNode(tView, index, 4 /* TNodeType.Container */, tagName || null, getConstant(tViewConsts, attrsIndex));
- resolveDirectives(tView, lView, tNode, getConstant(tViewConsts, localRefsIndex));
- registerPostOrderHooks(tView, tNode);
- const embeddedTView = tNode.tView = createTView(2 /* TViewType.Embedded */, tNode, templateFn, decls, vars, tView.directiveRegistry, tView.pipeRegistry, null, tView.schemas, tViewConsts, null /* ssrId */);
- if (tView.queries !== null) {
- tView.queries.template(tView, tNode);
- embeddedTView.queries = tView.queries.embeddedTView(tNode);
- }
- return tNode;
- }
- /**
- * Creates an LContainer for an ng-template (dynamically-inserted view), e.g.
- *
- * <ng-template #foo>
- * <div></div>
- * </ng-template>
- *
- * @param index The index of the container in the data array
- * @param templateFn Inline template
- * @param decls The number of nodes, local refs, and pipes for this template
- * @param vars The number of bindings for this template
- * @param tagName The name of the container element, if applicable
- * @param attrsIndex Index of template attributes in the `consts` array.
- * @param localRefs Index of the local references in the `consts` array.
- * @param localRefExtractor A function which extracts local-refs values from the template.
- * Defaults to the current element associated with the local-ref.
- *
- * @codeGenApi
- */
- function ɵɵtemplate(index, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex, localRefExtractor) {
- const lView = getLView();
- const tView = getTView();
- const adjustedIndex = index + HEADER_OFFSET;
- const tNode = tView.firstCreatePass ? templateFirstCreatePass(adjustedIndex, tView, lView, templateFn, decls, vars, tagName, attrsIndex, localRefsIndex) :
- tView.data[adjustedIndex];
- setCurrentTNode(tNode, false);
- const comment = _locateOrCreateContainerAnchor(tView, lView, tNode, index);
- if (wasLastNodeCreated()) {
- appendChild(tView, lView, comment, tNode);
- }
- attachPatchData(comment, lView);
- addToViewTree(lView, lView[adjustedIndex] = createLContainer(comment, lView, comment, tNode));
- if (isDirectiveHost(tNode)) {
- createDirectivesInstances(tView, lView, tNode);
- }
- if (localRefsIndex != null) {
- saveResolvedLocalsInData(lView, tNode, localRefExtractor);
- }
- }
- let _locateOrCreateContainerAnchor = createContainerAnchorImpl;
- /**
- * Regular creation mode for LContainers and their anchor (comment) nodes.
- */
- function createContainerAnchorImpl(tView, lView, tNode, index) {
- lastNodeWasCreated(true);
- return lView[RENDERER].createComment(ngDevMode ? 'container' : '');
- }
- /**
- * Enables hydration code path (to lookup existing elements in DOM)
- * in addition to the regular creation mode for LContainers and their
- * anchor (comment) nodes.
- */
- function locateOrCreateContainerAnchorImpl(tView, lView, tNode, index) {
- const hydrationInfo = lView[HYDRATION];
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
- lastNodeWasCreated(isNodeCreationMode);
- // Regular creation mode.
- if (isNodeCreationMode) {
- return createContainerAnchorImpl(tView, lView, tNode, index);
- }
- const ssrId = hydrationInfo.data[TEMPLATES]?.[index] ?? null;
- // Apply `ssrId` value to the underlying TView if it was not previously set.
- //
- // There might be situations when the same component is present in a template
- // multiple times and some instances are opted-out of using hydration via
- // `ngSkipHydration` attribute. In this scenario, at the time a TView is created,
- // the `ssrId` might be `null` (if the first component is opted-out of hydration).
- // The code below makes sure that the `ssrId` is applied to the TView if it's still
- // `null` and verifies we never try to override it with a different value.
- if (ssrId !== null && tNode.tView !== null) {
- if (tNode.tView.ssrId === null) {
- tNode.tView.ssrId = ssrId;
- }
- else {
- ngDevMode &&
- assertEqual(tNode.tView.ssrId, ssrId, 'Unexpected value of the `ssrId` for this TView');
- }
- }
- // Hydration mode, looking up existing elements in DOM.
- const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode);
- ngDevMode && validateNodeExists(currentRNode, lView, tNode);
- setSegmentHead(hydrationInfo, index, currentRNode);
- const viewContainerSize = calcSerializedContainerSize(hydrationInfo, index);
- const comment = siblingAfter(viewContainerSize, currentRNode);
- if (ngDevMode) {
- validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
- markRNodeAsClaimedByHydration(comment);
- }
- return comment;
- }
- function enableLocateOrCreateContainerAnchorImpl() {
- _locateOrCreateContainerAnchor = locateOrCreateContainerAnchorImpl;
- }
- /** Store a value in the `data` at a given `index`. */
- function store(tView, lView, index, value) {
- // We don't store any static data for local variables, so the first time
- // we see the template, we should store as null to avoid a sparse array
- if (index >= tView.data.length) {
- tView.data[index] = null;
- tView.blueprint[index] = null;
- }
- lView[index] = value;
- }
- /**
- * Retrieves a local reference from the current contextViewData.
- *
- * If the reference to retrieve is in a parent view, this instruction is used in conjunction
- * with a nextContext() call, which walks up the tree and updates the contextViewData instance.
- *
- * @param index The index of the local ref in contextViewData.
- *
- * @codeGenApi
- */
- function ɵɵreference(index) {
- const contextLView = getContextLView();
- return load(contextLView, HEADER_OFFSET + index);
- }
- /**
- * Update a property on a selected element.
- *
- * Operates on the element selected by index via the {@link select} instruction.
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled
- *
- * @param propName Name of property. Because it is going to DOM, this is not subject to
- * renaming as part of minification.
- * @param value New value to write.
- * @param sanitizer An optional function used to sanitize the value.
- * @returns This function returns itself so that it may be chained
- * (e.g. `property('name', ctx.name)('title', ctx.title)`)
- *
- * @codeGenApi
- */
- function ɵɵproperty(propName, value, sanitizer) {
- const lView = getLView();
- const bindingIndex = nextBindingIndex();
- if (bindingUpdated(lView, bindingIndex, value)) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false);
- ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
- }
- return ɵɵproperty;
- }
- /**
- * Given `<div style="..." my-dir>` and `MyDir` with `@Input('style')` we need to write to
- * directive input.
- */
- function setDirectiveInputsWhichShadowsStyling(tView, tNode, lView, value, isClassBased) {
- const inputs = tNode.inputs;
- const property = isClassBased ? 'class' : 'style';
- // We support both 'class' and `className` hence the fallback.
- setInputsForProperty(tView, lView, inputs[property], property, value);
- }
- function elementStartFirstCreatePass(index, tView, lView, name, attrsIndex, localRefsIndex) {
- ngDevMode && assertFirstCreatePass(tView);
- ngDevMode && ngDevMode.firstCreatePass++;
- const tViewConsts = tView.consts;
- const attrs = getConstant(tViewConsts, attrsIndex);
- const tNode = getOrCreateTNode(tView, index, 2 /* TNodeType.Element */, name, attrs);
- resolveDirectives(tView, lView, tNode, getConstant(tViewConsts, localRefsIndex));
- if (tNode.attrs !== null) {
- computeStaticStyling(tNode, tNode.attrs, false);
- }
- if (tNode.mergedAttrs !== null) {
- computeStaticStyling(tNode, tNode.mergedAttrs, true);
- }
- if (tView.queries !== null) {
- tView.queries.elementStart(tView, tNode);
- }
- return tNode;
- }
- /**
- * Create DOM element. The instruction must later be followed by `elementEnd()` call.
- *
- * @param index Index of the element in the LView array
- * @param name Name of the DOM Node
- * @param attrsIndex Index of the element's attributes in the `consts` array.
- * @param localRefsIndex Index of the element's local references in the `consts` array.
- * @returns This function returns itself so that it may be chained.
- *
- * Attributes and localRefs are passed as an array of strings where elements with an even index
- * hold an attribute name and elements with an odd index hold an attribute value, ex.:
- * ['id', 'warning5', 'class', 'alert']
- *
- * @codeGenApi
- */
- function ɵɵelementStart(index, name, attrsIndex, localRefsIndex) {
- const lView = getLView();
- const tView = getTView();
- const adjustedIndex = HEADER_OFFSET + index;
- ngDevMode &&
- assertEqual(getBindingIndex(), tView.bindingStartIndex, 'elements should be created before any bindings');
- ngDevMode && assertIndexInRange(lView, adjustedIndex);
- const renderer = lView[RENDERER];
- const tNode = tView.firstCreatePass ?
- elementStartFirstCreatePass(adjustedIndex, tView, lView, name, attrsIndex, localRefsIndex) :
- tView.data[adjustedIndex];
- const native = _locateOrCreateElementNode(tView, lView, tNode, renderer, name, index);
- lView[adjustedIndex] = native;
- const hasDirectives = isDirectiveHost(tNode);
- if (ngDevMode && tView.firstCreatePass) {
- validateElementIsKnown(native, lView, tNode.value, tView.schemas, hasDirectives);
- }
- setCurrentTNode(tNode, true);
- setupStaticAttributes(renderer, native, tNode);
- if ((tNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */ && wasLastNodeCreated()) {
- // In the i18n case, the translation may have removed this element, so only add it if it is not
- // detached. See `TNodeType.Placeholder` and `LFrame.inI18n` for more context.
- appendChild(tView, lView, native, tNode);
- }
- // any immediate children of a component or template container must be pre-emptively
- // monkey-patched with the component view data so that the element can be inspected
- // later on using any element discovery utility methods (see `element_discovery.ts`)
- if (getElementDepthCount() === 0) {
- attachPatchData(native, lView);
- }
- increaseElementDepthCount();
- if (hasDirectives) {
- createDirectivesInstances(tView, lView, tNode);
- executeContentQueries(tView, tNode, lView);
- }
- if (localRefsIndex !== null) {
- saveResolvedLocalsInData(lView, tNode);
- }
- return ɵɵelementStart;
- }
- /**
- * Mark the end of the element.
- * @returns This function returns itself so that it may be chained.
- *
- * @codeGenApi
- */
- function ɵɵelementEnd() {
- let currentTNode = getCurrentTNode();
- ngDevMode && assertDefined(currentTNode, 'No parent node to close.');
- if (isCurrentTNodeParent()) {
- setCurrentTNodeAsNotParent();
- }
- else {
- ngDevMode && assertHasParent(getCurrentTNode());
- currentTNode = currentTNode.parent;
- setCurrentTNode(currentTNode, false);
- }
- const tNode = currentTNode;
- ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */);
- if (isSkipHydrationRootTNode(tNode)) {
- leaveSkipHydrationBlock();
- }
- decreaseElementDepthCount();
- const tView = getTView();
- if (tView.firstCreatePass) {
- registerPostOrderHooks(tView, currentTNode);
- if (isContentQueryHost(currentTNode)) {
- tView.queries.elementEnd(currentTNode);
- }
- }
- if (tNode.classesWithoutHost != null && hasClassInput(tNode)) {
- setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.classesWithoutHost, true);
- }
- if (tNode.stylesWithoutHost != null && hasStyleInput(tNode)) {
- setDirectiveInputsWhichShadowsStyling(tView, tNode, getLView(), tNode.stylesWithoutHost, false);
- }
- return ɵɵelementEnd;
- }
- /**
- * Creates an empty element using {@link elementStart} and {@link elementEnd}
- *
- * @param index Index of the element in the data array
- * @param name Name of the DOM Node
- * @param attrsIndex Index of the element's attributes in the `consts` array.
- * @param localRefsIndex Index of the element's local references in the `consts` array.
- * @returns This function returns itself so that it may be chained.
- *
- * @codeGenApi
- */
- function ɵɵelement(index, name, attrsIndex, localRefsIndex) {
- ɵɵelementStart(index, name, attrsIndex, localRefsIndex);
- ɵɵelementEnd();
- return ɵɵelement;
- }
- let _locateOrCreateElementNode = (tView, lView, tNode, renderer, name, index) => {
- lastNodeWasCreated(true);
- return createElementNode(renderer, name, getNamespace$1());
- };
- /**
- * Enables hydration code path (to lookup existing elements in DOM)
- * in addition to the regular creation mode of element nodes.
- */
- function locateOrCreateElementNodeImpl(tView, lView, tNode, renderer, name, index) {
- const hydrationInfo = lView[HYDRATION];
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
- lastNodeWasCreated(isNodeCreationMode);
- // Regular creation mode.
- if (isNodeCreationMode) {
- return createElementNode(renderer, name, getNamespace$1());
- }
- // Hydration mode, looking up an existing element in DOM.
- const native = locateNextRNode(hydrationInfo, tView, lView, tNode);
- ngDevMode && validateMatchingNode(native, Node.ELEMENT_NODE, name, lView, tNode);
- ngDevMode && markRNodeAsClaimedByHydration(native);
- // This element might also be an anchor of a view container.
- if (getSerializedContainerViews(hydrationInfo, index)) {
- // Important note: this element acts as an anchor, but it's **not** a part
- // of the embedded view, so we start the segment **after** this element, taking
- // a reference to the next sibling. For example, the following template:
- // `<div #vcrTarget>` is represented in the DOM as `<div></div>...<!--container-->`,
- // so while processing a `<div>` instruction, point to the next sibling as a
- // start of a segment.
- ngDevMode && validateNodeExists(native.nextSibling, lView, tNode);
- setSegmentHead(hydrationInfo, index, native.nextSibling);
- }
- // Checks if the skip hydration attribute is present during hydration so we know to
- // skip attempting to hydrate this block. We check both TNode and RElement for an
- // attribute: the RElement case is needed for i18n cases, when we add it to host
- // elements during the annotation phase (after all internal data structures are setup).
- if (hydrationInfo &&
- (hasSkipHydrationAttrOnTNode(tNode) || hasSkipHydrationAttrOnRElement(native))) {
- if (isComponentHost(tNode)) {
- enterSkipHydrationBlock(tNode);
- // Since this isn't hydratable, we need to empty the node
- // so there's no duplicate content after render
- clearElementContents(native);
- ngDevMode && ngDevMode.componentsSkippedHydration++;
- }
- else if (ngDevMode) {
- // If this is not a component host, throw an error.
- // Hydration can be skipped on per-component basis only.
- throw invalidSkipHydrationHost(native);
- }
- }
- return native;
- }
- function enableLocateOrCreateElementNodeImpl() {
- _locateOrCreateElementNode = locateOrCreateElementNodeImpl;
- }
- function elementContainerStartFirstCreatePass(index, tView, lView, attrsIndex, localRefsIndex) {
- ngDevMode && ngDevMode.firstCreatePass++;
- const tViewConsts = tView.consts;
- const attrs = getConstant(tViewConsts, attrsIndex);
- const tNode = getOrCreateTNode(tView, index, 8 /* TNodeType.ElementContainer */, 'ng-container', attrs);
- // While ng-container doesn't necessarily support styling, we use the style context to identify
- // and execute directives on the ng-container.
- if (attrs !== null) {
- computeStaticStyling(tNode, attrs, true);
- }
- const localRefs = getConstant(tViewConsts, localRefsIndex);
- resolveDirectives(tView, lView, tNode, localRefs);
- if (tView.queries !== null) {
- tView.queries.elementStart(tView, tNode);
- }
- return tNode;
- }
- /**
- * Creates a logical container for other nodes (<ng-container>) backed by a comment node in the DOM.
- * The instruction must later be followed by `elementContainerEnd()` call.
- *
- * @param index Index of the element in the LView array
- * @param attrsIndex Index of the container attributes in the `consts` array.
- * @param localRefsIndex Index of the container's local references in the `consts` array.
- * @returns This function returns itself so that it may be chained.
- *
- * Even if this instruction accepts a set of attributes no actual attribute values are propagated to
- * the DOM (as a comment node can't have attributes). Attributes are here only for directive
- * matching purposes and setting initial inputs of directives.
- *
- * @codeGenApi
- */
- function ɵɵelementContainerStart(index, attrsIndex, localRefsIndex) {
- const lView = getLView();
- const tView = getTView();
- const adjustedIndex = index + HEADER_OFFSET;
- ngDevMode && assertIndexInRange(lView, adjustedIndex);
- ngDevMode &&
- assertEqual(getBindingIndex(), tView.bindingStartIndex, 'element containers should be created before any bindings');
- const tNode = tView.firstCreatePass ?
- elementContainerStartFirstCreatePass(adjustedIndex, tView, lView, attrsIndex, localRefsIndex) :
- tView.data[adjustedIndex];
- setCurrentTNode(tNode, true);
- const comment = _locateOrCreateElementContainerNode(tView, lView, tNode, index);
- lView[adjustedIndex] = comment;
- if (wasLastNodeCreated()) {
- appendChild(tView, lView, comment, tNode);
- }
- attachPatchData(comment, lView);
- if (isDirectiveHost(tNode)) {
- createDirectivesInstances(tView, lView, tNode);
- executeContentQueries(tView, tNode, lView);
- }
- if (localRefsIndex != null) {
- saveResolvedLocalsInData(lView, tNode);
- }
- return ɵɵelementContainerStart;
- }
- /**
- * Mark the end of the <ng-container>.
- * @returns This function returns itself so that it may be chained.
- *
- * @codeGenApi
- */
- function ɵɵelementContainerEnd() {
- let currentTNode = getCurrentTNode();
- const tView = getTView();
- if (isCurrentTNodeParent()) {
- setCurrentTNodeAsNotParent();
- }
- else {
- ngDevMode && assertHasParent(currentTNode);
- currentTNode = currentTNode.parent;
- setCurrentTNode(currentTNode, false);
- }
- ngDevMode && assertTNodeType(currentTNode, 8 /* TNodeType.ElementContainer */);
- if (tView.firstCreatePass) {
- registerPostOrderHooks(tView, currentTNode);
- if (isContentQueryHost(currentTNode)) {
- tView.queries.elementEnd(currentTNode);
- }
- }
- return ɵɵelementContainerEnd;
- }
- /**
- * Creates an empty logical container using {@link elementContainerStart}
- * and {@link elementContainerEnd}
- *
- * @param index Index of the element in the LView array
- * @param attrsIndex Index of the container attributes in the `consts` array.
- * @param localRefsIndex Index of the container's local references in the `consts` array.
- * @returns This function returns itself so that it may be chained.
- *
- * @codeGenApi
- */
- function ɵɵelementContainer(index, attrsIndex, localRefsIndex) {
- ɵɵelementContainerStart(index, attrsIndex, localRefsIndex);
- ɵɵelementContainerEnd();
- return ɵɵelementContainer;
- }
- let _locateOrCreateElementContainerNode = (tView, lView, tNode, index) => {
- lastNodeWasCreated(true);
- return createCommentNode(lView[RENDERER], ngDevMode ? 'ng-container' : '');
- };
- /**
- * Enables hydration code path (to lookup existing elements in DOM)
- * in addition to the regular creation mode of comment nodes that
- * represent <ng-container>'s anchor.
- */
- function locateOrCreateElementContainerNode(tView, lView, tNode, index) {
- let comment;
- const hydrationInfo = lView[HYDRATION];
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
- lastNodeWasCreated(isNodeCreationMode);
- // Regular creation mode.
- if (isNodeCreationMode) {
- return createCommentNode(lView[RENDERER], ngDevMode ? 'ng-container' : '');
- }
- // Hydration mode, looking up existing elements in DOM.
- const currentRNode = locateNextRNode(hydrationInfo, tView, lView, tNode);
- ngDevMode && validateNodeExists(currentRNode, lView, tNode);
- const ngContainerSize = getNgContainerSize(hydrationInfo, index);
- ngDevMode &&
- assertNumber(ngContainerSize, 'Unexpected state: hydrating an <ng-container>, ' +
- 'but no hydration info is available.');
- setSegmentHead(hydrationInfo, index, currentRNode);
- comment = siblingAfter(ngContainerSize, currentRNode);
- if (ngDevMode) {
- validateMatchingNode(comment, Node.COMMENT_NODE, null, lView, tNode);
- markRNodeAsClaimedByHydration(comment);
- }
- return comment;
- }
- function enableLocateOrCreateElementContainerNodeImpl() {
- _locateOrCreateElementContainerNode = locateOrCreateElementContainerNode;
- }
- /**
- * Returns the current OpaqueViewState instance.
- *
- * Used in conjunction with the restoreView() instruction to save a snapshot
- * of the current view and restore it when listeners are invoked. This allows
- * walking the declaration view tree in listeners to get vars from parent views.
- *
- * @codeGenApi
- */
- function ɵɵgetCurrentView() {
- return getLView();
- }
- /**
- * Determine if the argument is shaped like a Promise
- */
- function isPromise(obj) {
- // allow any Promise/A+ compliant thenable.
- // It's up to the caller to ensure that obj.then conforms to the spec
- return !!obj && typeof obj.then === 'function';
- }
- /**
- * Determine if the argument is a Subscribable
- */
- function isSubscribable(obj) {
- return !!obj && typeof obj.subscribe === 'function';
- }
- /**
- * Adds an event listener to the current node.
- *
- * If an output exists on one of the node's directives, it also subscribes to the output
- * and saves the subscription for later cleanup.
- *
- * @param eventName Name of the event
- * @param listenerFn The function to be called when event emits
- * @param useCapture Whether or not to use capture in event listener - this argument is a reminder
- * from the Renderer3 infrastructure and should be removed from the instruction arguments
- * @param eventTargetResolver Function that returns global target information in case this listener
- * should be attached to a global object like window, document or body
- *
- * @codeGenApi
- */
- function ɵɵlistener(eventName, listenerFn, useCapture, eventTargetResolver) {
- const lView = getLView();
- const tView = getTView();
- const tNode = getCurrentTNode();
- listenerInternal(tView, lView, lView[RENDERER], tNode, eventName, listenerFn, eventTargetResolver);
- return ɵɵlistener;
- }
- /**
- * Registers a synthetic host listener (e.g. `(@foo.start)`) on a component or directive.
- *
- * This instruction is for compatibility purposes and is designed to ensure that a
- * synthetic host listener (e.g. `@HostListener('@foo.start')`) properly gets rendered
- * in the component's renderer. Normally all host listeners are evaluated with the
- * parent component's renderer, but, in the case of animation @triggers, they need
- * to be evaluated with the sub component's renderer (because that's where the
- * animation triggers are defined).
- *
- * Do not use this instruction as a replacement for `listener`. This instruction
- * only exists to ensure compatibility with the ViewEngine's host binding behavior.
- *
- * @param eventName Name of the event
- * @param listenerFn The function to be called when event emits
- * @param useCapture Whether or not to use capture in event listener
- * @param eventTargetResolver Function that returns global target information in case this listener
- * should be attached to a global object like window, document or body
- *
- * @codeGenApi
- */
- function ɵɵsyntheticHostListener(eventName, listenerFn) {
- const tNode = getCurrentTNode();
- const lView = getLView();
- const tView = getTView();
- const currentDef = getCurrentDirectiveDef(tView.data);
- const renderer = loadComponentRenderer(currentDef, tNode, lView);
- listenerInternal(tView, lView, renderer, tNode, eventName, listenerFn);
- return ɵɵsyntheticHostListener;
- }
- /**
- * A utility function that checks if a given element has already an event handler registered for an
- * event with a specified name. The TView.cleanup data structure is used to find out which events
- * are registered for a given element.
- */
- function findExistingListener(tView, lView, eventName, tNodeIdx) {
- const tCleanup = tView.cleanup;
- if (tCleanup != null) {
- for (let i = 0; i < tCleanup.length - 1; i += 2) {
- const cleanupEventName = tCleanup[i];
- if (cleanupEventName === eventName && tCleanup[i + 1] === tNodeIdx) {
- // We have found a matching event name on the same node but it might not have been
- // registered yet, so we must explicitly verify entries in the LView cleanup data
- // structures.
- const lCleanup = lView[CLEANUP];
- const listenerIdxInLCleanup = tCleanup[i + 2];
- return lCleanup.length > listenerIdxInLCleanup ? lCleanup[listenerIdxInLCleanup] : null;
- }
- // TView.cleanup can have a mix of 4-elements entries (for event handler cleanups) or
- // 2-element entries (for directive and queries destroy hooks). As such we can encounter
- // blocks of 4 or 2 items in the tView.cleanup and this is why we iterate over 2 elements
- // first and jump another 2 elements if we detect listeners cleanup (4 elements). Also check
- // documentation of TView.cleanup for more details of this data structure layout.
- if (typeof cleanupEventName === 'string') {
- i += 2;
- }
- }
- }
- return null;
- }
- function listenerInternal(tView, lView, renderer, tNode, eventName, listenerFn, eventTargetResolver) {
- const isTNodeDirectiveHost = isDirectiveHost(tNode);
- const firstCreatePass = tView.firstCreatePass;
- const tCleanup = firstCreatePass && getOrCreateTViewCleanup(tView);
- const context = lView[CONTEXT];
- // When the ɵɵlistener instruction was generated and is executed we know that there is either a
- // native listener or a directive output on this element. As such we we know that we will have to
- // register a listener and store its cleanup function on LView.
- const lCleanup = getOrCreateLViewCleanup(lView);
- ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */);
- let processOutputs = true;
- // Adding a native event listener is applicable when:
- // - The corresponding TNode represents a DOM element.
- // - The event target has a resolver (usually resulting in a global object,
- // such as `window` or `document`).
- if ((tNode.type & 3 /* TNodeType.AnyRNode */) || eventTargetResolver) {
- const native = getNativeByTNode(tNode, lView);
- const target = eventTargetResolver ? eventTargetResolver(native) : native;
- const lCleanupIndex = lCleanup.length;
- const idxOrTargetGetter = eventTargetResolver ?
- (_lView) => eventTargetResolver(unwrapRNode(_lView[tNode.index])) :
- tNode.index;
- // In order to match current behavior, native DOM event listeners must be added for all
- // events (including outputs).
- // There might be cases where multiple directives on the same element try to register an event
- // handler function for the same event. In this situation we want to avoid registration of
- // several native listeners as each registration would be intercepted by NgZone and
- // trigger change detection. This would mean that a single user action would result in several
- // change detections being invoked. To avoid this situation we want to have only one call to
- // native handler registration (for the same element and same type of event).
- //
- // In order to have just one native event handler in presence of multiple handler functions,
- // we just register a first handler function as a native event listener and then chain
- // (coalesce) other handler functions on top of the first native handler function.
- let existingListener = null;
- // Please note that the coalescing described here doesn't happen for events specifying an
- // alternative target (ex. (document:click)) - this is to keep backward compatibility with the
- // view engine.
- // Also, we don't have to search for existing listeners is there are no directives
- // matching on a given node as we can't register multiple event handlers for the same event in
- // a template (this would mean having duplicate attributes).
- if (!eventTargetResolver && isTNodeDirectiveHost) {
- existingListener = findExistingListener(tView, lView, eventName, tNode.index);
- }
- if (existingListener !== null) {
- // Attach a new listener to coalesced listeners list, maintaining the order in which
- // listeners are registered. For performance reasons, we keep a reference to the last
- // listener in that list (in `__ngLastListenerFn__` field), so we can avoid going through
- // the entire set each time we need to add a new listener.
- const lastListenerFn = existingListener.__ngLastListenerFn__ || existingListener;
- lastListenerFn.__ngNextListenerFn__ = listenerFn;
- existingListener.__ngLastListenerFn__ = listenerFn;
- processOutputs = false;
- }
- else {
- listenerFn = wrapListener(tNode, lView, context, listenerFn, false /** preventDefault */);
- const cleanupFn = renderer.listen(target, eventName, listenerFn);
- ngDevMode && ngDevMode.rendererAddEventListener++;
- lCleanup.push(listenerFn, cleanupFn);
- tCleanup && tCleanup.push(eventName, idxOrTargetGetter, lCleanupIndex, lCleanupIndex + 1);
- }
- }
- else {
- // Even if there is no native listener to add, we still need to wrap the listener so that OnPush
- // ancestors are marked dirty when an event occurs.
- listenerFn = wrapListener(tNode, lView, context, listenerFn, false /** preventDefault */);
- }
- // subscribe to directive outputs
- const outputs = tNode.outputs;
- let props;
- if (processOutputs && outputs !== null && (props = outputs[eventName])) {
- const propsLength = props.length;
- if (propsLength) {
- for (let i = 0; i < propsLength; i += 2) {
- const index = props[i];
- ngDevMode && assertIndexInRange(lView, index);
- const minifiedName = props[i + 1];
- const directiveInstance = lView[index];
- const output = directiveInstance[minifiedName];
- if (ngDevMode && !isSubscribable(output)) {
- throw new Error(`@Output ${minifiedName} not initialized in '${directiveInstance.constructor.name}'.`);
- }
- const subscription = output.subscribe(listenerFn);
- const idx = lCleanup.length;
- lCleanup.push(listenerFn, subscription);
- tCleanup && tCleanup.push(eventName, tNode.index, idx, -(idx + 1));
- }
- }
- }
- }
- function executeListenerWithErrorHandling(lView, context, listenerFn, e) {
- try {
- profiler(6 /* ProfilerEvent.OutputStart */, context, listenerFn);
- // Only explicitly returning false from a listener should preventDefault
- return listenerFn(e) !== false;
- }
- catch (error) {
- handleError(lView, error);
- return false;
- }
- finally {
- profiler(7 /* ProfilerEvent.OutputEnd */, context, listenerFn);
- }
- }
- /**
- * Wraps an event listener with a function that marks ancestors dirty and prevents default behavior,
- * if applicable.
- *
- * @param tNode The TNode associated with this listener
- * @param lView The LView that contains this listener
- * @param listenerFn The listener function to call
- * @param wrapWithPreventDefault Whether or not to prevent default behavior
- * (the procedural renderer does this already, so in those cases, we should skip)
- */
- function wrapListener(tNode, lView, context, listenerFn, wrapWithPreventDefault) {
- // Note: we are performing most of the work in the listener function itself
- // to optimize listener registration.
- return function wrapListenerIn_markDirtyAndPreventDefault(e) {
- // Ivy uses `Function` as a special token that allows us to unwrap the function
- // so that it can be invoked programmatically by `DebugNode.triggerEventHandler`.
- if (e === Function) {
- return listenerFn;
- }
- // In order to be backwards compatible with View Engine, events on component host nodes
- // must also mark the component view itself dirty (i.e. the view that it owns).
- const startView = tNode.componentOffset > -1 ? getComponentLViewByIndex(tNode.index, lView) : lView;
- markViewDirty(startView);
- let result = executeListenerWithErrorHandling(lView, context, listenerFn, e);
- // A just-invoked listener function might have coalesced listeners so we need to check for
- // their presence and invoke as needed.
- let nextListenerFn = wrapListenerIn_markDirtyAndPreventDefault.__ngNextListenerFn__;
- while (nextListenerFn) {
- // We should prevent default if any of the listeners explicitly return false
- result = executeListenerWithErrorHandling(lView, context, nextListenerFn, e) && result;
- nextListenerFn = nextListenerFn.__ngNextListenerFn__;
- }
- if (wrapWithPreventDefault && result === false) {
- e.preventDefault();
- }
- return result;
- };
- }
- /**
- * Retrieves a context at the level specified and saves it as the global, contextViewData.
- * Will get the next level up if level is not specified.
- *
- * This is used to save contexts of parent views so they can be bound in embedded views, or
- * in conjunction with reference() to bind a ref from a parent view.
- *
- * @param level The relative level of the view from which to grab context compared to contextVewData
- * @returns context
- *
- * @codeGenApi
- */
- function ɵɵnextContext(level = 1) {
- return nextContextImpl(level);
- }
- /**
- * Checks a given node against matching projection slots and returns the
- * determined slot index. Returns "null" if no slot matched the given node.
- *
- * This function takes into account the parsed ngProjectAs selector from the
- * node's attributes. If present, it will check whether the ngProjectAs selector
- * matches any of the projection slot selectors.
- */
- function matchingProjectionSlotIndex(tNode, projectionSlots) {
- let wildcardNgContentIndex = null;
- const ngProjectAsAttrVal = getProjectAsAttrValue(tNode);
- for (let i = 0; i < projectionSlots.length; i++) {
- const slotValue = projectionSlots[i];
- // The last wildcard projection slot should match all nodes which aren't matching
- // any selector. This is necessary to be backwards compatible with view engine.
- if (slotValue === '*') {
- wildcardNgContentIndex = i;
- continue;
- }
- // If we ran into an `ngProjectAs` attribute, we should match its parsed selector
- // to the list of selectors, otherwise we fall back to matching against the node.
- if (ngProjectAsAttrVal === null ?
- isNodeMatchingSelectorList(tNode, slotValue, /* isProjectionMode */ true) :
- isSelectorInSelectorList(ngProjectAsAttrVal, slotValue)) {
- return i; // first matching selector "captures" a given node
- }
- }
- return wildcardNgContentIndex;
- }
- /**
- * Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
- * It takes all the selectors from the entire component's template and decides where
- * each projected node belongs (it re-distributes nodes among "buckets" where each "bucket" is
- * backed by a selector).
- *
- * This function requires CSS selectors to be provided in 2 forms: parsed (by a compiler) and text,
- * un-parsed form.
- *
- * The parsed form is needed for efficient matching of a node against a given CSS selector.
- * The un-parsed, textual form is needed for support of the ngProjectAs attribute.
- *
- * Having a CSS selector in 2 different formats is not ideal, but alternatives have even more
- * drawbacks:
- * - having only a textual form would require runtime parsing of CSS selectors;
- * - we can't have only a parsed as we can't re-construct textual form from it (as entered by a
- * template author).
- *
- * @param projectionSlots? A collection of projection slots. A projection slot can be based
- * on a parsed CSS selectors or set to the wildcard selector ("*") in order to match
- * all nodes which do not match any selector. If not specified, a single wildcard
- * selector projection slot will be defined.
- *
- * @codeGenApi
- */
- function ɵɵprojectionDef(projectionSlots) {
- const componentNode = getLView()[DECLARATION_COMPONENT_VIEW][T_HOST];
- if (!componentNode.projection) {
- // If no explicit projection slots are defined, fall back to a single
- // projection slot with the wildcard selector.
- const numProjectionSlots = projectionSlots ? projectionSlots.length : 1;
- const projectionHeads = componentNode.projection =
- newArray(numProjectionSlots, null);
- const tails = projectionHeads.slice();
- let componentChild = componentNode.child;
- while (componentChild !== null) {
- const slotIndex = projectionSlots ? matchingProjectionSlotIndex(componentChild, projectionSlots) : 0;
- if (slotIndex !== null) {
- if (tails[slotIndex]) {
- tails[slotIndex].projectionNext = componentChild;
- }
- else {
- projectionHeads[slotIndex] = componentChild;
- }
- tails[slotIndex] = componentChild;
- }
- componentChild = componentChild.next;
- }
- }
- }
- /**
- * Inserts previously re-distributed projected nodes. This instruction must be preceded by a call
- * to the projectionDef instruction.
- *
- * @param nodeIndex
- * @param selectorIndex:
- * - 0 when the selector is `*` (or unspecified as this is the default value),
- * - 1 based index of the selector from the {@link projectionDef}
- *
- * @codeGenApi
- */
- function ɵɵprojection(nodeIndex, selectorIndex = 0, attrs) {
- const lView = getLView();
- const tView = getTView();
- const tProjectionNode = getOrCreateTNode(tView, HEADER_OFFSET + nodeIndex, 16 /* TNodeType.Projection */, null, attrs || null);
- // We can't use viewData[HOST_NODE] because projection nodes can be nested in embedded views.
- if (tProjectionNode.projection === null)
- tProjectionNode.projection = selectorIndex;
- // `<ng-content>` has no content
- setCurrentTNodeAsNotParent();
- const hydrationInfo = lView[HYDRATION];
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1();
- if (isNodeCreationMode &&
- (tProjectionNode.flags & 32 /* TNodeFlags.isDetached */) !== 32 /* TNodeFlags.isDetached */) {
- // re-distribution of projectable nodes is stored on a component's view level
- applyProjection(tView, lView, tProjectionNode);
- }
- }
- /**
- *
- * Update an interpolated property on an element with a lone bound value
- *
- * Used when the value passed to a property has 1 interpolated value in it, an no additional text
- * surrounds that interpolated value:
- *
- * ```html
- * <div title="{{v0}}"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate('title', v0);
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate(propName, v0, sanitizer) {
- ɵɵpropertyInterpolate1(propName, '', v0, '', sanitizer);
- return ɵɵpropertyInterpolate;
- }
- /**
- *
- * Update an interpolated property on an element with single bound value surrounded by text.
- *
- * Used when the value passed to a property has 1 interpolated value in it:
- *
- * ```html
- * <div title="prefix{{v0}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate1('title', 'prefix', v0, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate1(propName, prefix, v0, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 1, prefix, suffix);
- }
- return ɵɵpropertyInterpolate1;
- }
- /**
- *
- * Update an interpolated property on an element with 2 bound values surrounded by text.
- *
- * Used when the value passed to a property has 2 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate2('title', 'prefix', v0, '-', v1, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate2(propName, prefix, v0, i0, v1, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 2, prefix, i0, suffix);
- }
- return ɵɵpropertyInterpolate2;
- }
- /**
- *
- * Update an interpolated property on an element with 3 bound values surrounded by text.
- *
- * Used when the value passed to a property has 3 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}-{{v2}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate3(
- * 'title', 'prefix', v0, '-', v1, '-', v2, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate3(propName, prefix, v0, i0, v1, i1, v2, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 3, prefix, i0, i1, suffix);
- }
- return ɵɵpropertyInterpolate3;
- }
- /**
- *
- * Update an interpolated property on an element with 4 bound values surrounded by text.
- *
- * Used when the value passed to a property has 4 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate4(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate4(propName, prefix, v0, i0, v1, i1, v2, i2, v3, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 4, prefix, i0, i1, i2, suffix);
- }
- return ɵɵpropertyInterpolate4;
- }
- /**
- *
- * Update an interpolated property on an element with 5 bound values surrounded by text.
- *
- * Used when the value passed to a property has 5 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate5(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate5(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 5, prefix, i0, i1, i2, i3, suffix);
- }
- return ɵɵpropertyInterpolate5;
- }
- /**
- *
- * Update an interpolated property on an element with 6 bound values surrounded by text.
- *
- * Used when the value passed to a property has 6 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate6(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate6(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 6, prefix, i0, i1, i2, i3, i4, suffix);
- }
- return ɵɵpropertyInterpolate6;
- }
- /**
- *
- * Update an interpolated property on an element with 7 bound values surrounded by text.
- *
- * Used when the value passed to a property has 7 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate7(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate7(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 7, prefix, i0, i1, i2, i3, i4, i5, suffix);
- }
- return ɵɵpropertyInterpolate7;
- }
- /**
- *
- * Update an interpolated property on an element with 8 bound values surrounded by text.
- *
- * Used when the value passed to a property has 8 interpolated values in it:
- *
- * ```html
- * <div title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolate8(
- * 'title', 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix');
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param i6 Static value used for concatenation only.
- * @param v7 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolate8(propName, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- ngDevMode &&
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - 8, prefix, i0, i1, i2, i3, i4, i5, i6, suffix);
- }
- return ɵɵpropertyInterpolate8;
- }
- /**
- * Update an interpolated property on an element with 9 or more bound values surrounded by text.
- *
- * Used when the number of interpolated values exceeds 8.
- *
- * ```html
- * <div
- * title="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix"></div>
- * ```
- *
- * Its compiled representation is::
- *
- * ```ts
- * ɵɵpropertyInterpolateV(
- * 'title', ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9,
- * 'suffix']);
- * ```
- *
- * If the property name also exists as an input property on one of the element's directives,
- * the component property will be set instead of the element property. This check must
- * be conducted at runtime so child components that add new `@Inputs` don't have to be re-compiled.
- *
- * @param propName The name of the property to update.
- * @param values The collection of values and the strings in between those values, beginning with a
- * string prefix and ending with a string suffix.
- * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
- * @param sanitizer An optional sanitizer function
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵpropertyInterpolateV(propName, values, sanitizer) {
- const lView = getLView();
- const interpolatedValue = interpolationV(lView, values);
- if (interpolatedValue !== NO_CHANGE) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, interpolatedValue, lView[RENDERER], sanitizer, false);
- if (ngDevMode) {
- const interpolationInBetween = [values[0]]; // prefix
- for (let i = 2; i < values.length; i += 2) {
- interpolationInBetween.push(values[i]);
- }
- storePropertyBindingMetadata(tView.data, tNode, propName, getBindingIndex() - interpolationInBetween.length + 1, ...interpolationInBetween);
- }
- }
- return ɵɵpropertyInterpolateV;
- }
- function toTStylingRange(prev, next) {
- ngDevMode && assertNumberInRange(prev, 0, 32767 /* StylingRange.UNSIGNED_MASK */);
- ngDevMode && assertNumberInRange(next, 0, 32767 /* StylingRange.UNSIGNED_MASK */);
- return (prev << 17 /* StylingRange.PREV_SHIFT */ | next << 2 /* StylingRange.NEXT_SHIFT */);
- }
- function getTStylingRangePrev(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- return (tStylingRange >> 17 /* StylingRange.PREV_SHIFT */) & 32767 /* StylingRange.UNSIGNED_MASK */;
- }
- function getTStylingRangePrevDuplicate(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- return (tStylingRange & 2 /* StylingRange.PREV_DUPLICATE */) == 2 /* StylingRange.PREV_DUPLICATE */;
- }
- function setTStylingRangePrev(tStylingRange, previous) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- ngDevMode && assertNumberInRange(previous, 0, 32767 /* StylingRange.UNSIGNED_MASK */);
- return ((tStylingRange & ~4294836224 /* StylingRange.PREV_MASK */) | (previous << 17 /* StylingRange.PREV_SHIFT */));
- }
- function setTStylingRangePrevDuplicate(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- return (tStylingRange | 2 /* StylingRange.PREV_DUPLICATE */);
- }
- function getTStylingRangeNext(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- return (tStylingRange & 131068 /* StylingRange.NEXT_MASK */) >> 2 /* StylingRange.NEXT_SHIFT */;
- }
- function setTStylingRangeNext(tStylingRange, next) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- ngDevMode && assertNumberInRange(next, 0, 32767 /* StylingRange.UNSIGNED_MASK */);
- return ((tStylingRange & ~131068 /* StylingRange.NEXT_MASK */) | //
- next << 2 /* StylingRange.NEXT_SHIFT */);
- }
- function getTStylingRangeNextDuplicate(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- return ((tStylingRange) & 1 /* StylingRange.NEXT_DUPLICATE */) === 1 /* StylingRange.NEXT_DUPLICATE */;
- }
- function setTStylingRangeNextDuplicate(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- return (tStylingRange | 1 /* StylingRange.NEXT_DUPLICATE */);
- }
- function getTStylingRangeTail(tStylingRange) {
- ngDevMode && assertNumber(tStylingRange, 'expected number');
- const next = getTStylingRangeNext(tStylingRange);
- return next === 0 ? getTStylingRangePrev(tStylingRange) : next;
- }
- /**
- * NOTE: The word `styling` is used interchangeably as style or class styling.
- *
- * This file contains code to link styling instructions together so that they can be replayed in
- * priority order. The file exists because Ivy styling instruction execution order does not match
- * that of the priority order. The purpose of this code is to create a linked list so that the
- * instructions can be traversed in priority order when computing the styles.
- *
- * Assume we are dealing with the following code:
- * ```
- * @Component({
- * template: `
- * <my-cmp [style]=" {color: '#001'} "
- * [style.color]=" #002 "
- * dir-style-color-1
- * dir-style-color-2> `
- * })
- * class ExampleComponent {
- * static ngComp = ... {
- * ...
- * // Compiler ensures that `ɵɵstyleProp` is after `ɵɵstyleMap`
- * ɵɵstyleMap({color: '#001'});
- * ɵɵstyleProp('color', '#002');
- * ...
- * }
- * }
- *
- * @Directive({
- * selector: `[dir-style-color-1]',
- * })
- * class Style1Directive {
- * @HostBinding('style') style = {color: '#005'};
- * @HostBinding('style.color') color = '#006';
- *
- * static ngDir = ... {
- * ...
- * // Compiler ensures that `ɵɵstyleProp` is after `ɵɵstyleMap`
- * ɵɵstyleMap({color: '#005'});
- * ɵɵstyleProp('color', '#006');
- * ...
- * }
- * }
- *
- * @Directive({
- * selector: `[dir-style-color-2]',
- * })
- * class Style2Directive {
- * @HostBinding('style') style = {color: '#007'};
- * @HostBinding('style.color') color = '#008';
- *
- * static ngDir = ... {
- * ...
- * // Compiler ensures that `ɵɵstyleProp` is after `ɵɵstyleMap`
- * ɵɵstyleMap({color: '#007'});
- * ɵɵstyleProp('color', '#008');
- * ...
- * }
- * }
- *
- * @Directive({
- * selector: `my-cmp',
- * })
- * class MyComponent {
- * @HostBinding('style') style = {color: '#003'};
- * @HostBinding('style.color') color = '#004';
- *
- * static ngComp = ... {
- * ...
- * // Compiler ensures that `ɵɵstyleProp` is after `ɵɵstyleMap`
- * ɵɵstyleMap({color: '#003'});
- * ɵɵstyleProp('color', '#004');
- * ...
- * }
- * }
- * ```
- *
- * The Order of instruction execution is:
- *
- * NOTE: the comment binding location is for illustrative purposes only.
- *
- * ```
- * // Template: (ExampleComponent)
- * ɵɵstyleMap({color: '#001'}); // Binding index: 10
- * ɵɵstyleProp('color', '#002'); // Binding index: 12
- * // MyComponent
- * ɵɵstyleMap({color: '#003'}); // Binding index: 20
- * ɵɵstyleProp('color', '#004'); // Binding index: 22
- * // Style1Directive
- * ɵɵstyleMap({color: '#005'}); // Binding index: 24
- * ɵɵstyleProp('color', '#006'); // Binding index: 26
- * // Style2Directive
- * ɵɵstyleMap({color: '#007'}); // Binding index: 28
- * ɵɵstyleProp('color', '#008'); // Binding index: 30
- * ```
- *
- * The correct priority order of concatenation is:
- *
- * ```
- * // MyComponent
- * ɵɵstyleMap({color: '#003'}); // Binding index: 20
- * ɵɵstyleProp('color', '#004'); // Binding index: 22
- * // Style1Directive
- * ɵɵstyleMap({color: '#005'}); // Binding index: 24
- * ɵɵstyleProp('color', '#006'); // Binding index: 26
- * // Style2Directive
- * ɵɵstyleMap({color: '#007'}); // Binding index: 28
- * ɵɵstyleProp('color', '#008'); // Binding index: 30
- * // Template: (ExampleComponent)
- * ɵɵstyleMap({color: '#001'}); // Binding index: 10
- * ɵɵstyleProp('color', '#002'); // Binding index: 12
- * ```
- *
- * What color should be rendered?
- *
- * Once the items are correctly sorted in the list, the answer is simply the last item in the
- * concatenation list which is `#002`.
- *
- * To do so we keep a linked list of all of the bindings which pertain to this element.
- * Notice that the bindings are inserted in the order of execution, but the `TView.data` allows
- * us to traverse them in the order of priority.
- *
- * |Idx|`TView.data`|`LView` | Notes
- * |---|------------|-----------------|--------------
- * |...| | |
- * |10 |`null` |`{color: '#001'}`| `ɵɵstyleMap('color', {color: '#001'})`
- * |11 |`30 | 12` | ... |
- * |12 |`color` |`'#002'` | `ɵɵstyleProp('color', '#002')`
- * |13 |`10 | 0` | ... |
- * |...| | |
- * |20 |`null` |`{color: '#003'}`| `ɵɵstyleMap('color', {color: '#003'})`
- * |21 |`0 | 22` | ... |
- * |22 |`color` |`'#004'` | `ɵɵstyleProp('color', '#004')`
- * |23 |`20 | 24` | ... |
- * |24 |`null` |`{color: '#005'}`| `ɵɵstyleMap('color', {color: '#005'})`
- * |25 |`22 | 26` | ... |
- * |26 |`color` |`'#006'` | `ɵɵstyleProp('color', '#006')`
- * |27 |`24 | 28` | ... |
- * |28 |`null` |`{color: '#007'}`| `ɵɵstyleMap('color', {color: '#007'})`
- * |29 |`26 | 30` | ... |
- * |30 |`color` |`'#008'` | `ɵɵstyleProp('color', '#008')`
- * |31 |`28 | 10` | ... |
- *
- * The above data structure allows us to re-concatenate the styling no matter which data binding
- * changes.
- *
- * NOTE: in addition to keeping track of next/previous index the `TView.data` also stores prev/next
- * duplicate bit. The duplicate bit if true says there either is a binding with the same name or
- * there is a map (which may contain the name). This information is useful in knowing if other
- * styles with higher priority need to be searched for overwrites.
- *
- * NOTE: See `should support example in 'tnode_linked_list.ts' documentation` in
- * `tnode_linked_list_spec.ts` for working example.
- */
- let __unused_const_as_closure_does_not_like_standalone_comment_blocks__;
- /**
- * Insert new `tStyleValue` at `TData` and link existing style bindings such that we maintain linked
- * list of styles and compute the duplicate flag.
- *
- * Note: this function is executed during `firstUpdatePass` only to populate the `TView.data`.
- *
- * The function works by keeping track of `tStylingRange` which contains two pointers pointing to
- * the head/tail of the template portion of the styles.
- * - if `isHost === false` (we are template) then insertion is at tail of `TStylingRange`
- * - if `isHost === true` (we are host binding) then insertion is at head of `TStylingRange`
- *
- * @param tData The `TData` to insert into.
- * @param tNode `TNode` associated with the styling element.
- * @param tStylingKey See `TStylingKey`.
- * @param index location of where `tStyleValue` should be stored (and linked into list.)
- * @param isHostBinding `true` if the insertion is for a `hostBinding`. (insertion is in front of
- * template.)
- * @param isClassBinding True if the associated `tStylingKey` as a `class` styling.
- * `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.)
- */
- function insertTStylingBinding(tData, tNode, tStylingKeyWithStatic, index, isHostBinding, isClassBinding) {
- ngDevMode && assertFirstUpdatePass(getTView());
- let tBindings = isClassBinding ? tNode.classBindings : tNode.styleBindings;
- let tmplHead = getTStylingRangePrev(tBindings);
- let tmplTail = getTStylingRangeNext(tBindings);
- tData[index] = tStylingKeyWithStatic;
- let isKeyDuplicateOfStatic = false;
- let tStylingKey;
- if (Array.isArray(tStylingKeyWithStatic)) {
- // We are case when the `TStylingKey` contains static fields as well.
- const staticKeyValueArray = tStylingKeyWithStatic;
- tStylingKey = staticKeyValueArray[1]; // unwrap.
- // We need to check if our key is present in the static so that we can mark it as duplicate.
- if (tStylingKey === null ||
- keyValueArrayIndexOf(staticKeyValueArray, tStylingKey) > 0) {
- // tStylingKey is present in the statics, need to mark it as duplicate.
- isKeyDuplicateOfStatic = true;
- }
- }
- else {
- tStylingKey = tStylingKeyWithStatic;
- }
- if (isHostBinding) {
- // We are inserting host bindings
- // If we don't have template bindings then `tail` is 0.
- const hasTemplateBindings = tmplTail !== 0;
- // This is important to know because that means that the `head` can't point to the first
- // template bindings (there are none.) Instead the head points to the tail of the template.
- if (hasTemplateBindings) {
- // template head's "prev" will point to last host binding or to 0 if no host bindings yet
- const previousNode = getTStylingRangePrev(tData[tmplHead + 1]);
- tData[index + 1] = toTStylingRange(previousNode, tmplHead);
- // if a host binding has already been registered, we need to update the next of that host
- // binding to point to this one
- if (previousNode !== 0) {
- // We need to update the template-tail value to point to us.
- tData[previousNode + 1] =
- setTStylingRangeNext(tData[previousNode + 1], index);
- }
- // The "previous" of the template binding head should point to this host binding
- tData[tmplHead + 1] = setTStylingRangePrev(tData[tmplHead + 1], index);
- }
- else {
- tData[index + 1] = toTStylingRange(tmplHead, 0);
- // if a host binding has already been registered, we need to update the next of that host
- // binding to point to this one
- if (tmplHead !== 0) {
- // We need to update the template-tail value to point to us.
- tData[tmplHead + 1] = setTStylingRangeNext(tData[tmplHead + 1], index);
- }
- // if we don't have template, the head points to template-tail, and needs to be advanced.
- tmplHead = index;
- }
- }
- else {
- // We are inserting in template section.
- // We need to set this binding's "previous" to the current template tail
- tData[index + 1] = toTStylingRange(tmplTail, 0);
- ngDevMode &&
- assertEqual(tmplHead !== 0 && tmplTail === 0, false, 'Adding template bindings after hostBindings is not allowed.');
- if (tmplHead === 0) {
- tmplHead = index;
- }
- else {
- // We need to update the previous value "next" to point to this binding
- tData[tmplTail + 1] = setTStylingRangeNext(tData[tmplTail + 1], index);
- }
- tmplTail = index;
- }
- // Now we need to update / compute the duplicates.
- // Starting with our location search towards head (least priority)
- if (isKeyDuplicateOfStatic) {
- tData[index + 1] = setTStylingRangePrevDuplicate(tData[index + 1]);
- }
- markDuplicates(tData, tStylingKey, index, true, isClassBinding);
- markDuplicates(tData, tStylingKey, index, false, isClassBinding);
- markDuplicateOfResidualStyling(tNode, tStylingKey, tData, index, isClassBinding);
- tBindings = toTStylingRange(tmplHead, tmplTail);
- if (isClassBinding) {
- tNode.classBindings = tBindings;
- }
- else {
- tNode.styleBindings = tBindings;
- }
- }
- /**
- * Look into the residual styling to see if the current `tStylingKey` is duplicate of residual.
- *
- * @param tNode `TNode` where the residual is stored.
- * @param tStylingKey `TStylingKey` to store.
- * @param tData `TData` associated with the current `LView`.
- * @param index location of where `tStyleValue` should be stored (and linked into list.)
- * @param isClassBinding True if the associated `tStylingKey` as a `class` styling.
- * `tNode.classBindings` should be used (or `tNode.styleBindings` otherwise.)
- */
- function markDuplicateOfResidualStyling(tNode, tStylingKey, tData, index, isClassBinding) {
- const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles;
- if (residual != null /* or undefined */ && typeof tStylingKey == 'string' &&
- keyValueArrayIndexOf(residual, tStylingKey) >= 0) {
- // We have duplicate in the residual so mark ourselves as duplicate.
- tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1]);
- }
- }
- /**
- * Marks `TStyleValue`s as duplicates if another style binding in the list has the same
- * `TStyleValue`.
- *
- * NOTE: this function is intended to be called twice once with `isPrevDir` set to `true` and once
- * with it set to `false` to search both the previous as well as next items in the list.
- *
- * No duplicate case
- * ```
- * [style.color]
- * [style.width.px] <<- index
- * [style.height.px]
- * ```
- *
- * In the above case adding `[style.width.px]` to the existing `[style.color]` produces no
- * duplicates because `width` is not found in any other part of the linked list.
- *
- * Duplicate case
- * ```
- * [style.color]
- * [style.width.em]
- * [style.width.px] <<- index
- * ```
- * In the above case adding `[style.width.px]` will produce a duplicate with `[style.width.em]`
- * because `width` is found in the chain.
- *
- * Map case 1
- * ```
- * [style.width.px]
- * [style.color]
- * [style] <<- index
- * ```
- * In the above case adding `[style]` will produce a duplicate with any other bindings because
- * `[style]` is a Map and as such is fully dynamic and could produce `color` or `width`.
- *
- * Map case 2
- * ```
- * [style]
- * [style.width.px]
- * [style.color] <<- index
- * ```
- * In the above case adding `[style.color]` will produce a duplicate because there is already a
- * `[style]` binding which is a Map and as such is fully dynamic and could produce `color` or
- * `width`.
- *
- * NOTE: Once `[style]` (Map) is added into the system all things are mapped as duplicates.
- * NOTE: We use `style` as example, but same logic is applied to `class`es as well.
- *
- * @param tData `TData` where the linked list is stored.
- * @param tStylingKey `TStylingKeyPrimitive` which contains the value to compare to other keys in
- * the linked list.
- * @param index Starting location in the linked list to search from
- * @param isPrevDir Direction.
- * - `true` for previous (lower priority);
- * - `false` for next (higher priority).
- */
- function markDuplicates(tData, tStylingKey, index, isPrevDir, isClassBinding) {
- const tStylingAtIndex = tData[index + 1];
- const isMap = tStylingKey === null;
- let cursor = isPrevDir ? getTStylingRangePrev(tStylingAtIndex) : getTStylingRangeNext(tStylingAtIndex);
- let foundDuplicate = false;
- // We keep iterating as long as we have a cursor
- // AND either:
- // - we found what we are looking for, OR
- // - we are a map in which case we have to continue searching even after we find what we were
- // looking for since we are a wild card and everything needs to be flipped to duplicate.
- while (cursor !== 0 && (foundDuplicate === false || isMap)) {
- ngDevMode && assertIndexInRange(tData, cursor);
- const tStylingValueAtCursor = tData[cursor];
- const tStyleRangeAtCursor = tData[cursor + 1];
- if (isStylingMatch(tStylingValueAtCursor, tStylingKey)) {
- foundDuplicate = true;
- tData[cursor + 1] = isPrevDir ? setTStylingRangeNextDuplicate(tStyleRangeAtCursor) :
- setTStylingRangePrevDuplicate(tStyleRangeAtCursor);
- }
- cursor = isPrevDir ? getTStylingRangePrev(tStyleRangeAtCursor) :
- getTStylingRangeNext(tStyleRangeAtCursor);
- }
- if (foundDuplicate) {
- // if we found a duplicate, than mark ourselves.
- tData[index + 1] = isPrevDir ? setTStylingRangePrevDuplicate(tStylingAtIndex) :
- setTStylingRangeNextDuplicate(tStylingAtIndex);
- }
- }
- /**
- * Determines if two `TStylingKey`s are a match.
- *
- * When computing whether a binding contains a duplicate, we need to compare if the instruction
- * `TStylingKey` has a match.
- *
- * Here are examples of `TStylingKey`s which match given `tStylingKeyCursor` is:
- * - `color`
- * - `color` // Match another color
- * - `null` // That means that `tStylingKey` is a `classMap`/`styleMap` instruction
- * - `['', 'color', 'other', true]` // wrapped `color` so match
- * - `['', null, 'other', true]` // wrapped `null` so match
- * - `['', 'width', 'color', 'value']` // wrapped static value contains a match on `'color'`
- * - `null` // `tStylingKeyCursor` always match as it is `classMap`/`styleMap` instruction
- *
- * @param tStylingKeyCursor
- * @param tStylingKey
- */
- function isStylingMatch(tStylingKeyCursor, tStylingKey) {
- ngDevMode &&
- assertNotEqual(Array.isArray(tStylingKey), true, 'Expected that \'tStylingKey\' has been unwrapped');
- if (tStylingKeyCursor === null || // If the cursor is `null` it means that we have map at that
- // location so we must assume that we have a match.
- tStylingKey == null || // If `tStylingKey` is `null` then it is a map therefor assume that it
- // contains a match.
- (Array.isArray(tStylingKeyCursor) ? tStylingKeyCursor[1] : tStylingKeyCursor) ===
- tStylingKey // If the keys match explicitly than we are a match.
- ) {
- return true;
- }
- else if (Array.isArray(tStylingKeyCursor) && typeof tStylingKey === 'string') {
- // if we did not find a match, but `tStylingKeyCursor` is `KeyValueArray` that means cursor has
- // statics and we need to check those as well.
- return keyValueArrayIndexOf(tStylingKeyCursor, tStylingKey) >=
- 0; // see if we are matching the key
- }
- return false;
- }
- // Global state of the parser. (This makes parser non-reentrant, but that is not an issue)
- const parserState = {
- textEnd: 0,
- key: 0,
- keyEnd: 0,
- value: 0,
- valueEnd: 0,
- };
- /**
- * Retrieves the last parsed `key` of style.
- * @param text the text to substring the key from.
- */
- function getLastParsedKey(text) {
- return text.substring(parserState.key, parserState.keyEnd);
- }
- /**
- * Retrieves the last parsed `value` of style.
- * @param text the text to substring the key from.
- */
- function getLastParsedValue(text) {
- return text.substring(parserState.value, parserState.valueEnd);
- }
- /**
- * Initializes `className` string for parsing and parses the first token.
- *
- * This function is intended to be used in this format:
- * ```
- * for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) {
- * const key = getLastParsedKey();
- * ...
- * }
- * ```
- * @param text `className` to parse
- * @returns index where the next invocation of `parseClassNameNext` should resume.
- */
- function parseClassName(text) {
- resetParserState(text);
- return parseClassNameNext(text, consumeWhitespace(text, 0, parserState.textEnd));
- }
- /**
- * Parses next `className` token.
- *
- * This function is intended to be used in this format:
- * ```
- * for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) {
- * const key = getLastParsedKey();
- * ...
- * }
- * ```
- *
- * @param text `className` to parse
- * @param index where the parsing should resume.
- * @returns index where the next invocation of `parseClassNameNext` should resume.
- */
- function parseClassNameNext(text, index) {
- const end = parserState.textEnd;
- if (end === index) {
- return -1;
- }
- index = parserState.keyEnd = consumeClassToken(text, parserState.key = index, end);
- return consumeWhitespace(text, index, end);
- }
- /**
- * Initializes `cssText` string for parsing and parses the first key/values.
- *
- * This function is intended to be used in this format:
- * ```
- * for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i))) {
- * const key = getLastParsedKey();
- * const value = getLastParsedValue();
- * ...
- * }
- * ```
- * @param text `cssText` to parse
- * @returns index where the next invocation of `parseStyleNext` should resume.
- */
- function parseStyle(text) {
- resetParserState(text);
- return parseStyleNext(text, consumeWhitespace(text, 0, parserState.textEnd));
- }
- /**
- * Parses the next `cssText` key/values.
- *
- * This function is intended to be used in this format:
- * ```
- * for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i))) {
- * const key = getLastParsedKey();
- * const value = getLastParsedValue();
- * ...
- * }
- *
- * @param text `cssText` to parse
- * @param index where the parsing should resume.
- * @returns index where the next invocation of `parseStyleNext` should resume.
- */
- function parseStyleNext(text, startIndex) {
- const end = parserState.textEnd;
- let index = parserState.key = consumeWhitespace(text, startIndex, end);
- if (end === index) {
- // we reached an end so just quit
- return -1;
- }
- index = parserState.keyEnd = consumeStyleKey(text, index, end);
- index = consumeSeparator(text, index, end, 58 /* CharCode.COLON */);
- index = parserState.value = consumeWhitespace(text, index, end);
- index = parserState.valueEnd = consumeStyleValue(text, index, end);
- return consumeSeparator(text, index, end, 59 /* CharCode.SEMI_COLON */);
- }
- /**
- * Reset the global state of the styling parser.
- * @param text The styling text to parse.
- */
- function resetParserState(text) {
- parserState.key = 0;
- parserState.keyEnd = 0;
- parserState.value = 0;
- parserState.valueEnd = 0;
- parserState.textEnd = text.length;
- }
- /**
- * Returns index of next non-whitespace character.
- *
- * @param text Text to scan
- * @param startIndex Starting index of character where the scan should start.
- * @param endIndex Ending index of character where the scan should end.
- * @returns Index of next non-whitespace character (May be the same as `start` if no whitespace at
- * that location.)
- */
- function consumeWhitespace(text, startIndex, endIndex) {
- while (startIndex < endIndex && text.charCodeAt(startIndex) <= 32 /* CharCode.SPACE */) {
- startIndex++;
- }
- return startIndex;
- }
- /**
- * Returns index of last char in class token.
- *
- * @param text Text to scan
- * @param startIndex Starting index of character where the scan should start.
- * @param endIndex Ending index of character where the scan should end.
- * @returns Index after last char in class token.
- */
- function consumeClassToken(text, startIndex, endIndex) {
- while (startIndex < endIndex && text.charCodeAt(startIndex) > 32 /* CharCode.SPACE */) {
- startIndex++;
- }
- return startIndex;
- }
- /**
- * Consumes all of the characters belonging to style key and token.
- *
- * @param text Text to scan
- * @param startIndex Starting index of character where the scan should start.
- * @param endIndex Ending index of character where the scan should end.
- * @returns Index after last style key character.
- */
- function consumeStyleKey(text, startIndex, endIndex) {
- let ch;
- while (startIndex < endIndex &&
- ((ch = text.charCodeAt(startIndex)) === 45 /* CharCode.DASH */ || ch === 95 /* CharCode.UNDERSCORE */ ||
- ((ch & -33 /* CharCode.UPPER_CASE */) >= 65 /* CharCode.A */ && (ch & -33 /* CharCode.UPPER_CASE */) <= 90 /* CharCode.Z */) ||
- (ch >= 48 /* CharCode.ZERO */ && ch <= 57 /* CharCode.NINE */))) {
- startIndex++;
- }
- return startIndex;
- }
- /**
- * Consumes all whitespace and the separator `:` after the style key.
- *
- * @param text Text to scan
- * @param startIndex Starting index of character where the scan should start.
- * @param endIndex Ending index of character where the scan should end.
- * @returns Index after separator and surrounding whitespace.
- */
- function consumeSeparator(text, startIndex, endIndex, separator) {
- startIndex = consumeWhitespace(text, startIndex, endIndex);
- if (startIndex < endIndex) {
- if (ngDevMode && text.charCodeAt(startIndex) !== separator) {
- malformedStyleError(text, String.fromCharCode(separator), startIndex);
- }
- startIndex++;
- }
- return startIndex;
- }
- /**
- * Consumes style value honoring `url()` and `""` text.
- *
- * @param text Text to scan
- * @param startIndex Starting index of character where the scan should start.
- * @param endIndex Ending index of character where the scan should end.
- * @returns Index after last style value character.
- */
- function consumeStyleValue(text, startIndex, endIndex) {
- let ch1 = -1; // 1st previous character
- let ch2 = -1; // 2nd previous character
- let ch3 = -1; // 3rd previous character
- let i = startIndex;
- let lastChIndex = i;
- while (i < endIndex) {
- const ch = text.charCodeAt(i++);
- if (ch === 59 /* CharCode.SEMI_COLON */) {
- return lastChIndex;
- }
- else if (ch === 34 /* CharCode.DOUBLE_QUOTE */ || ch === 39 /* CharCode.SINGLE_QUOTE */) {
- lastChIndex = i = consumeQuotedText(text, ch, i, endIndex);
- }
- else if (startIndex ===
- i - 4 && // We have seen only 4 characters so far "URL(" (Ignore "foo_URL()")
- ch3 === 85 /* CharCode.U */ &&
- ch2 === 82 /* CharCode.R */ && ch1 === 76 /* CharCode.L */ && ch === 40 /* CharCode.OPEN_PAREN */) {
- lastChIndex = i = consumeQuotedText(text, 41 /* CharCode.CLOSE_PAREN */, i, endIndex);
- }
- else if (ch > 32 /* CharCode.SPACE */) {
- // if we have a non-whitespace character then capture its location
- lastChIndex = i;
- }
- ch3 = ch2;
- ch2 = ch1;
- ch1 = ch & -33 /* CharCode.UPPER_CASE */;
- }
- return lastChIndex;
- }
- /**
- * Consumes all of the quoted characters.
- *
- * @param text Text to scan
- * @param quoteCharCode CharCode of either `"` or `'` quote or `)` for `url(...)`.
- * @param startIndex Starting index of character where the scan should start.
- * @param endIndex Ending index of character where the scan should end.
- * @returns Index after quoted characters.
- */
- function consumeQuotedText(text, quoteCharCode, startIndex, endIndex) {
- let ch1 = -1; // 1st previous character
- let index = startIndex;
- while (index < endIndex) {
- const ch = text.charCodeAt(index++);
- if (ch == quoteCharCode && ch1 !== 92 /* CharCode.BACK_SLASH */) {
- return index;
- }
- if (ch == 92 /* CharCode.BACK_SLASH */ && ch1 === 92 /* CharCode.BACK_SLASH */) {
- // two back slashes cancel each other out. For example `"\\"` should properly end the
- // quotation. (It should not assume that the last `"` is escaped.)
- ch1 = 0;
- }
- else {
- ch1 = ch;
- }
- }
- throw ngDevMode ? malformedStyleError(text, String.fromCharCode(quoteCharCode), endIndex) :
- new Error();
- }
- function malformedStyleError(text, expecting, index) {
- ngDevMode && assertEqual(typeof text === 'string', true, 'String expected here');
- throw throwError(`Malformed style at location ${index} in string '` + text.substring(0, index) + '[>>' +
- text.substring(index, index + 1) + '<<]' + text.slice(index + 1) +
- `'. Expecting '${expecting}'.`);
- }
- /**
- * Update a style binding on an element with the provided value.
- *
- * If the style value is falsy then it will be removed from the element
- * (or assigned a different value depending if there are any styles placed
- * on the element with `styleMap` or any static styles that are
- * present from when the element was created with `styling`).
- *
- * Note that the styling element is updated as part of `stylingApply`.
- *
- * @param prop A valid CSS property.
- * @param value New value to write (`null` or an empty string to remove).
- * @param suffix Optional suffix. Used with scalar values to add unit such as `px`.
- *
- * Note that this will apply the provided style value to the host element if this function is called
- * within a host binding function.
- *
- * @codeGenApi
- */
- function ɵɵstyleProp(prop, value, suffix) {
- checkStylingProperty(prop, value, suffix, false);
- return ɵɵstyleProp;
- }
- /**
- * Update a class binding on an element with the provided value.
- *
- * This instruction is meant to handle the `[class.foo]="exp"` case and,
- * therefore, the class binding itself must already be allocated using
- * `styling` within the creation block.
- *
- * @param prop A valid CSS class (only one).
- * @param value A true/false value which will turn the class on or off.
- *
- * Note that this will apply the provided class value to the host element if this function
- * is called within a host binding function.
- *
- * @codeGenApi
- */
- function ɵɵclassProp(className, value) {
- checkStylingProperty(className, value, null, true);
- return ɵɵclassProp;
- }
- /**
- * Update style bindings using an object literal on an element.
- *
- * This instruction is meant to apply styling via the `[style]="exp"` template bindings.
- * When styles are applied to the element they will then be updated with respect to
- * any styles/classes set via `styleProp`. If any styles are set to falsy
- * then they will be removed from the element.
- *
- * Note that the styling instruction will not be applied until `stylingApply` is called.
- *
- * @param styles A key/value style map of the styles that will be applied to the given element.
- * Any missing styles (that have already been applied to the element beforehand) will be
- * removed (unset) from the element's styling.
- *
- * Note that this will apply the provided styleMap value to the host element if this function
- * is called within a host binding.
- *
- * @codeGenApi
- */
- function ɵɵstyleMap(styles) {
- checkStylingMap(styleKeyValueArraySet, styleStringParser, styles, false);
- }
- /**
- * Parse text as style and add values to KeyValueArray.
- *
- * This code is pulled out to a separate function so that it can be tree shaken away if it is not
- * needed. It is only referenced from `ɵɵstyleMap`.
- *
- * @param keyValueArray KeyValueArray to add parsed values to.
- * @param text text to parse.
- */
- function styleStringParser(keyValueArray, text) {
- for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i)) {
- styleKeyValueArraySet(keyValueArray, getLastParsedKey(text), getLastParsedValue(text));
- }
- }
- /**
- * Update class bindings using an object literal or class-string on an element.
- *
- * This instruction is meant to apply styling via the `[class]="exp"` template bindings.
- * When classes are applied to the element they will then be updated with
- * respect to any styles/classes set via `classProp`. If any
- * classes are set to falsy then they will be removed from the element.
- *
- * Note that the styling instruction will not be applied until `stylingApply` is called.
- * Note that this will the provided classMap value to the host element if this function is called
- * within a host binding.
- *
- * @param classes A key/value map or string of CSS classes that will be added to the
- * given element. Any missing classes (that have already been applied to the element
- * beforehand) will be removed (unset) from the element's list of CSS classes.
- *
- * @codeGenApi
- */
- function ɵɵclassMap(classes) {
- checkStylingMap(classKeyValueArraySet, classStringParser, classes, true);
- }
- /**
- * Parse text as class and add values to KeyValueArray.
- *
- * This code is pulled out to a separate function so that it can be tree shaken away if it is not
- * needed. It is only referenced from `ɵɵclassMap`.
- *
- * @param keyValueArray KeyValueArray to add parsed values to.
- * @param text text to parse.
- */
- function classStringParser(keyValueArray, text) {
- for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) {
- keyValueArraySet(keyValueArray, getLastParsedKey(text), true);
- }
- }
- /**
- * Common code between `ɵɵclassProp` and `ɵɵstyleProp`.
- *
- * @param prop property name.
- * @param value binding value.
- * @param suffix suffix for the property (e.g. `em` or `px`)
- * @param isClassBased `true` if `class` change (`false` if `style`)
- */
- function checkStylingProperty(prop, value, suffix, isClassBased) {
- const lView = getLView();
- const tView = getTView();
- // Styling instructions use 2 slots per binding.
- // 1. one for the value / TStylingKey
- // 2. one for the intermittent-value / TStylingRange
- const bindingIndex = incrementBindingIndex(2);
- if (tView.firstUpdatePass) {
- stylingFirstUpdatePass(tView, prop, bindingIndex, isClassBased);
- }
- if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
- const tNode = tView.data[getSelectedIndex()];
- updateStyling(tView, tNode, lView, lView[RENDERER], prop, lView[bindingIndex + 1] = normalizeSuffix(value, suffix), isClassBased, bindingIndex);
- }
- }
- /**
- * Common code between `ɵɵclassMap` and `ɵɵstyleMap`.
- *
- * @param keyValueArraySet (See `keyValueArraySet` in "util/array_utils") Gets passed in as a
- * function so that `style` can be processed. This is done for tree shaking purposes.
- * @param stringParser Parser used to parse `value` if `string`. (Passed in as `style` and `class`
- * have different parsers.)
- * @param value bound value from application
- * @param isClassBased `true` if `class` change (`false` if `style`)
- */
- function checkStylingMap(keyValueArraySet, stringParser, value, isClassBased) {
- const tView = getTView();
- const bindingIndex = incrementBindingIndex(2);
- if (tView.firstUpdatePass) {
- stylingFirstUpdatePass(tView, null, bindingIndex, isClassBased);
- }
- const lView = getLView();
- if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
- // `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the
- // if so as not to read unnecessarily.
- const tNode = tView.data[getSelectedIndex()];
- if (hasStylingInputShadow(tNode, isClassBased) && !isInHostBindings(tView, bindingIndex)) {
- if (ngDevMode) {
- // verify that if we are shadowing then `TData` is appropriately marked so that we skip
- // processing this binding in styling resolution.
- const tStylingKey = tView.data[bindingIndex];
- assertEqual(Array.isArray(tStylingKey) ? tStylingKey[1] : tStylingKey, false, 'Styling linked list shadow input should be marked as \'false\'');
- }
- // VE does not concatenate the static portion like we are doing here.
- // Instead VE just ignores the static completely if dynamic binding is present.
- // Because of locality we have already set the static portion because we don't know if there
- // is a dynamic portion until later. If we would ignore the static portion it would look like
- // the binding has removed it. This would confuse `[ngStyle]`/`[ngClass]` to do the wrong
- // thing as it would think that the static portion was removed. For this reason we
- // concatenate it so that `[ngStyle]`/`[ngClass]` can continue to work on changed.
- let staticPrefix = isClassBased ? tNode.classesWithoutHost : tNode.stylesWithoutHost;
- ngDevMode && isClassBased === false && staticPrefix !== null &&
- assertEqual(staticPrefix.endsWith(';'), true, 'Expecting static portion to end with \';\'');
- if (staticPrefix !== null) {
- // We want to make sure that falsy values of `value` become empty strings.
- value = concatStringsWithSpace(staticPrefix, value ? value : '');
- }
- // Given `<div [style] my-dir>` such that `my-dir` has `@Input('style')`.
- // This takes over the `[style]` binding. (Same for `[class]`)
- setDirectiveInputsWhichShadowsStyling(tView, tNode, lView, value, isClassBased);
- }
- else {
- updateStylingMap(tView, tNode, lView, lView[RENDERER], lView[bindingIndex + 1], lView[bindingIndex + 1] = toStylingKeyValueArray(keyValueArraySet, stringParser, value), isClassBased, bindingIndex);
- }
- }
- }
- /**
- * Determines when the binding is in `hostBindings` section
- *
- * @param tView Current `TView`
- * @param bindingIndex index of binding which we would like if it is in `hostBindings`
- */
- function isInHostBindings(tView, bindingIndex) {
- // All host bindings are placed after the expando section.
- return bindingIndex >= tView.expandoStartIndex;
- }
- /**
- * Collects the necessary information to insert the binding into a linked list of style bindings
- * using `insertTStylingBinding`.
- *
- * @param tView `TView` where the binding linked list will be stored.
- * @param tStylingKey Property/key of the binding.
- * @param bindingIndex Index of binding associated with the `prop`
- * @param isClassBased `true` if `class` change (`false` if `style`)
- */
- function stylingFirstUpdatePass(tView, tStylingKey, bindingIndex, isClassBased) {
- ngDevMode && assertFirstUpdatePass(tView);
- const tData = tView.data;
- if (tData[bindingIndex + 1] === null) {
- // The above check is necessary because we don't clear first update pass until first successful
- // (no exception) template execution. This prevents the styling instruction from double adding
- // itself to the list.
- // `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the
- // if so as not to read unnecessarily.
- const tNode = tData[getSelectedIndex()];
- ngDevMode && assertDefined(tNode, 'TNode expected');
- const isHostBindings = isInHostBindings(tView, bindingIndex);
- if (hasStylingInputShadow(tNode, isClassBased) && tStylingKey === null && !isHostBindings) {
- // `tStylingKey === null` implies that we are either `[style]` or `[class]` binding.
- // If there is a directive which uses `@Input('style')` or `@Input('class')` than
- // we need to neutralize this binding since that directive is shadowing it.
- // We turn this into a noop by setting the key to `false`
- tStylingKey = false;
- }
- tStylingKey = wrapInStaticStylingKey(tData, tNode, tStylingKey, isClassBased);
- insertTStylingBinding(tData, tNode, tStylingKey, bindingIndex, isHostBindings, isClassBased);
- }
- }
- /**
- * Adds static styling information to the binding if applicable.
- *
- * The linked list of styles not only stores the list and keys, but also stores static styling
- * information on some of the keys. This function determines if the key should contain the styling
- * information and computes it.
- *
- * See `TStylingStatic` for more details.
- *
- * @param tData `TData` where the linked list is stored.
- * @param tNode `TNode` for which the styling is being computed.
- * @param stylingKey `TStylingKeyPrimitive` which may need to be wrapped into `TStylingKey`
- * @param isClassBased `true` if `class` (`false` if `style`)
- */
- function wrapInStaticStylingKey(tData, tNode, stylingKey, isClassBased) {
- const hostDirectiveDef = getCurrentDirectiveDef(tData);
- let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles;
- if (hostDirectiveDef === null) {
- // We are in template node.
- // If template node already had styling instruction then it has already collected the static
- // styling and there is no need to collect them again. We know that we are the first styling
- // instruction because the `TNode.*Bindings` points to 0 (nothing has been inserted yet).
- const isFirstStylingInstructionInTemplate = (isClassBased ? tNode.classBindings : tNode.styleBindings) === 0;
- if (isFirstStylingInstructionInTemplate) {
- // It would be nice to be able to get the statics from `mergeAttrs`, however, at this point
- // they are already merged and it would not be possible to figure which property belongs where
- // in the priority.
- stylingKey = collectStylingFromDirectives(null, tData, tNode, stylingKey, isClassBased);
- stylingKey = collectStylingFromTAttrs(stylingKey, tNode.attrs, isClassBased);
- // We know that if we have styling binding in template we can't have residual.
- residual = null;
- }
- }
- else {
- // We are in host binding node and there was no binding instruction in template node.
- // This means that we need to compute the residual.
- const directiveStylingLast = tNode.directiveStylingLast;
- const isFirstStylingInstructionInHostBinding = directiveStylingLast === -1 || tData[directiveStylingLast] !== hostDirectiveDef;
- if (isFirstStylingInstructionInHostBinding) {
- stylingKey =
- collectStylingFromDirectives(hostDirectiveDef, tData, tNode, stylingKey, isClassBased);
- if (residual === null) {
- // - If `null` than either:
- // - Template styling instruction already ran and it has consumed the static
- // styling into its `TStylingKey` and so there is no need to update residual. Instead
- // we need to update the `TStylingKey` associated with the first template node
- // instruction. OR
- // - Some other styling instruction ran and determined that there are no residuals
- let templateStylingKey = getTemplateHeadTStylingKey(tData, tNode, isClassBased);
- if (templateStylingKey !== undefined && Array.isArray(templateStylingKey)) {
- // Only recompute if `templateStylingKey` had static values. (If no static value found
- // then there is nothing to do since this operation can only produce less static keys, not
- // more.)
- templateStylingKey = collectStylingFromDirectives(null, tData, tNode, templateStylingKey[1] /* unwrap previous statics */, isClassBased);
- templateStylingKey =
- collectStylingFromTAttrs(templateStylingKey, tNode.attrs, isClassBased);
- setTemplateHeadTStylingKey(tData, tNode, isClassBased, templateStylingKey);
- }
- }
- else {
- // We only need to recompute residual if it is not `null`.
- // - If existing residual (implies there was no template styling). This means that some of
- // the statics may have moved from the residual to the `stylingKey` and so we have to
- // recompute.
- // - If `undefined` this is the first time we are running.
- residual = collectResidual(tData, tNode, isClassBased);
- }
- }
- }
- if (residual !== undefined) {
- isClassBased ? (tNode.residualClasses = residual) : (tNode.residualStyles = residual);
- }
- return stylingKey;
- }
- /**
- * Retrieve the `TStylingKey` for the template styling instruction.
- *
- * This is needed since `hostBinding` styling instructions are inserted after the template
- * instruction. While the template instruction needs to update the residual in `TNode` the
- * `hostBinding` instructions need to update the `TStylingKey` of the template instruction because
- * the template instruction is downstream from the `hostBindings` instructions.
- *
- * @param tData `TData` where the linked list is stored.
- * @param tNode `TNode` for which the styling is being computed.
- * @param isClassBased `true` if `class` (`false` if `style`)
- * @return `TStylingKey` if found or `undefined` if not found.
- */
- function getTemplateHeadTStylingKey(tData, tNode, isClassBased) {
- const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings;
- if (getTStylingRangeNext(bindings) === 0) {
- // There does not seem to be a styling instruction in the `template`.
- return undefined;
- }
- return tData[getTStylingRangePrev(bindings)];
- }
- /**
- * Update the `TStylingKey` of the first template instruction in `TNode`.
- *
- * Logically `hostBindings` styling instructions are of lower priority than that of the template.
- * However, they execute after the template styling instructions. This means that they get inserted
- * in front of the template styling instructions.
- *
- * If we have a template styling instruction and a new `hostBindings` styling instruction is
- * executed it means that it may need to steal static fields from the template instruction. This
- * method allows us to update the first template instruction `TStylingKey` with a new value.
- *
- * Assume:
- * ```
- * <div my-dir style="color: red" [style.color]="tmplExp"></div>
- *
- * @Directive({
- * host: {
- * 'style': 'width: 100px',
- * '[style.color]': 'dirExp',
- * }
- * })
- * class MyDir {}
- * ```
- *
- * when `[style.color]="tmplExp"` executes it creates this data structure.
- * ```
- * ['', 'color', 'color', 'red', 'width', '100px'],
- * ```
- *
- * The reason for this is that the template instruction does not know if there are styling
- * instructions and must assume that there are none and must collect all of the static styling.
- * (both
- * `color' and 'width`)
- *
- * When `'[style.color]': 'dirExp',` executes we need to insert a new data into the linked list.
- * ```
- * ['', 'color', 'width', '100px'], // newly inserted
- * ['', 'color', 'color', 'red', 'width', '100px'], // this is wrong
- * ```
- *
- * Notice that the template statics is now wrong as it incorrectly contains `width` so we need to
- * update it like so:
- * ```
- * ['', 'color', 'width', '100px'],
- * ['', 'color', 'color', 'red'], // UPDATE
- * ```
- *
- * @param tData `TData` where the linked list is stored.
- * @param tNode `TNode` for which the styling is being computed.
- * @param isClassBased `true` if `class` (`false` if `style`)
- * @param tStylingKey New `TStylingKey` which is replacing the old one.
- */
- function setTemplateHeadTStylingKey(tData, tNode, isClassBased, tStylingKey) {
- const bindings = isClassBased ? tNode.classBindings : tNode.styleBindings;
- ngDevMode &&
- assertNotEqual(getTStylingRangeNext(bindings), 0, 'Expecting to have at least one template styling binding.');
- tData[getTStylingRangePrev(bindings)] = tStylingKey;
- }
- /**
- * Collect all static values after the current `TNode.directiveStylingLast` index.
- *
- * Collect the remaining styling information which has not yet been collected by an existing
- * styling instruction.
- *
- * @param tData `TData` where the `DirectiveDefs` are stored.
- * @param tNode `TNode` which contains the directive range.
- * @param isClassBased `true` if `class` (`false` if `style`)
- */
- function collectResidual(tData, tNode, isClassBased) {
- let residual = undefined;
- const directiveEnd = tNode.directiveEnd;
- ngDevMode &&
- assertNotEqual(tNode.directiveStylingLast, -1, 'By the time this function gets called at least one hostBindings-node styling instruction must have executed.');
- // We add `1 + tNode.directiveStart` because we need to skip the current directive (as we are
- // collecting things after the last `hostBindings` directive which had a styling instruction.)
- for (let i = 1 + tNode.directiveStylingLast; i < directiveEnd; i++) {
- const attrs = tData[i].hostAttrs;
- residual = collectStylingFromTAttrs(residual, attrs, isClassBased);
- }
- return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased);
- }
- /**
- * Collect the static styling information with lower priority than `hostDirectiveDef`.
- *
- * (This is opposite of residual styling.)
- *
- * @param hostDirectiveDef `DirectiveDef` for which we want to collect lower priority static
- * styling. (Or `null` if template styling)
- * @param tData `TData` where the linked list is stored.
- * @param tNode `TNode` for which the styling is being computed.
- * @param stylingKey Existing `TStylingKey` to update or wrap.
- * @param isClassBased `true` if `class` (`false` if `style`)
- */
- function collectStylingFromDirectives(hostDirectiveDef, tData, tNode, stylingKey, isClassBased) {
- // We need to loop because there can be directives which have `hostAttrs` but don't have
- // `hostBindings` so this loop catches up to the current directive..
- let currentDirective = null;
- const directiveEnd = tNode.directiveEnd;
- let directiveStylingLast = tNode.directiveStylingLast;
- if (directiveStylingLast === -1) {
- directiveStylingLast = tNode.directiveStart;
- }
- else {
- directiveStylingLast++;
- }
- while (directiveStylingLast < directiveEnd) {
- currentDirective = tData[directiveStylingLast];
- ngDevMode && assertDefined(currentDirective, 'expected to be defined');
- stylingKey = collectStylingFromTAttrs(stylingKey, currentDirective.hostAttrs, isClassBased);
- if (currentDirective === hostDirectiveDef)
- break;
- directiveStylingLast++;
- }
- if (hostDirectiveDef !== null) {
- // we only advance the styling cursor if we are collecting data from host bindings.
- // Template executes before host bindings and so if we would update the index,
- // host bindings would not get their statics.
- tNode.directiveStylingLast = directiveStylingLast;
- }
- return stylingKey;
- }
- /**
- * Convert `TAttrs` into `TStylingStatic`.
- *
- * @param stylingKey existing `TStylingKey` to update or wrap.
- * @param attrs `TAttributes` to process.
- * @param isClassBased `true` if `class` (`false` if `style`)
- */
- function collectStylingFromTAttrs(stylingKey, attrs, isClassBased) {
- const desiredMarker = isClassBased ? 1 /* AttributeMarker.Classes */ : 2 /* AttributeMarker.Styles */;
- let currentMarker = -1 /* AttributeMarker.ImplicitAttributes */;
- if (attrs !== null) {
- for (let i = 0; i < attrs.length; i++) {
- const item = attrs[i];
- if (typeof item === 'number') {
- currentMarker = item;
- }
- else {
- if (currentMarker === desiredMarker) {
- if (!Array.isArray(stylingKey)) {
- stylingKey = stylingKey === undefined ? [] : ['', stylingKey];
- }
- keyValueArraySet(stylingKey, item, isClassBased ? true : attrs[++i]);
- }
- }
- }
- }
- return stylingKey === undefined ? null : stylingKey;
- }
- /**
- * Convert user input to `KeyValueArray`.
- *
- * This function takes user input which could be `string`, Object literal, or iterable and converts
- * it into a consistent representation. The output of this is `KeyValueArray` (which is an array
- * where
- * even indexes contain keys and odd indexes contain values for those keys).
- *
- * The advantage of converting to `KeyValueArray` is that we can perform diff in an input
- * independent
- * way.
- * (ie we can compare `foo bar` to `['bar', 'baz'] and determine a set of changes which need to be
- * applied)
- *
- * The fact that `KeyValueArray` is sorted is very important because it allows us to compute the
- * difference in linear fashion without the need to allocate any additional data.
- *
- * For example if we kept this as a `Map` we would have to iterate over previous `Map` to determine
- * which values need to be deleted, over the new `Map` to determine additions, and we would have to
- * keep additional `Map` to keep track of duplicates or items which have not yet been visited.
- *
- * @param keyValueArraySet (See `keyValueArraySet` in "util/array_utils") Gets passed in as a
- * function so that `style` can be processed. This is done
- * for tree shaking purposes.
- * @param stringParser The parser is passed in so that it will be tree shakable. See
- * `styleStringParser` and `classStringParser`
- * @param value The value to parse/convert to `KeyValueArray`
- */
- function toStylingKeyValueArray(keyValueArraySet, stringParser, value) {
- if (value == null /*|| value === undefined */ || value === '')
- return EMPTY_ARRAY;
- const styleKeyValueArray = [];
- const unwrappedValue = unwrapSafeValue(value);
- if (Array.isArray(unwrappedValue)) {
- for (let i = 0; i < unwrappedValue.length; i++) {
- keyValueArraySet(styleKeyValueArray, unwrappedValue[i], true);
- }
- }
- else if (typeof unwrappedValue === 'object') {
- for (const key in unwrappedValue) {
- if (unwrappedValue.hasOwnProperty(key)) {
- keyValueArraySet(styleKeyValueArray, key, unwrappedValue[key]);
- }
- }
- }
- else if (typeof unwrappedValue === 'string') {
- stringParser(styleKeyValueArray, unwrappedValue);
- }
- else {
- ngDevMode &&
- throwError('Unsupported styling type ' + typeof unwrappedValue + ': ' + unwrappedValue);
- }
- return styleKeyValueArray;
- }
- /**
- * Set a `value` for a `key`.
- *
- * See: `keyValueArraySet` for details
- *
- * @param keyValueArray KeyValueArray to add to.
- * @param key Style key to add.
- * @param value The value to set.
- */
- function styleKeyValueArraySet(keyValueArray, key, value) {
- keyValueArraySet(keyValueArray, key, unwrapSafeValue(value));
- }
- /**
- * Class-binding-specific function for setting the `value` for a `key`.
- *
- * See: `keyValueArraySet` for details
- *
- * @param keyValueArray KeyValueArray to add to.
- * @param key Style key to add.
- * @param value The value to set.
- */
- function classKeyValueArraySet(keyValueArray, key, value) {
- // We use `classList.add` to eventually add the CSS classes to the DOM node. Any value passed into
- // `add` is stringified and added to the `class` attribute, e.g. even null, undefined or numbers
- // will be added. Stringify the key here so that our internal data structure matches the value in
- // the DOM. The only exceptions are empty strings and strings that contain spaces for which
- // the browser throws an error. We ignore such values, because the error is somewhat cryptic.
- const stringKey = String(key);
- if (stringKey !== '' && !stringKey.includes(' ')) {
- keyValueArraySet(keyValueArray, stringKey, value);
- }
- }
- /**
- * Update map based styling.
- *
- * Map based styling could be anything which contains more than one binding. For example `string`,
- * or object literal. Dealing with all of these types would complicate the logic so
- * instead this function expects that the complex input is first converted into normalized
- * `KeyValueArray`. The advantage of normalization is that we get the values sorted, which makes it
- * very cheap to compute deltas between the previous and current value.
- *
- * @param tView Associated `TView.data` contains the linked list of binding priorities.
- * @param tNode `TNode` where the binding is located.
- * @param lView `LView` contains the values associated with other styling binding at this `TNode`.
- * @param renderer Renderer to use if any updates.
- * @param oldKeyValueArray Previous value represented as `KeyValueArray`
- * @param newKeyValueArray Current value represented as `KeyValueArray`
- * @param isClassBased `true` if `class` (`false` if `style`)
- * @param bindingIndex Binding index of the binding.
- */
- function updateStylingMap(tView, tNode, lView, renderer, oldKeyValueArray, newKeyValueArray, isClassBased, bindingIndex) {
- if (oldKeyValueArray === NO_CHANGE) {
- // On first execution the oldKeyValueArray is NO_CHANGE => treat it as empty KeyValueArray.
- oldKeyValueArray = EMPTY_ARRAY;
- }
- let oldIndex = 0;
- let newIndex = 0;
- let oldKey = 0 < oldKeyValueArray.length ? oldKeyValueArray[0] : null;
- let newKey = 0 < newKeyValueArray.length ? newKeyValueArray[0] : null;
- while (oldKey !== null || newKey !== null) {
- ngDevMode && assertLessThan(oldIndex, 999, 'Are we stuck in infinite loop?');
- ngDevMode && assertLessThan(newIndex, 999, 'Are we stuck in infinite loop?');
- const oldValue = oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex + 1] : undefined;
- const newValue = newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex + 1] : undefined;
- let setKey = null;
- let setValue = undefined;
- if (oldKey === newKey) {
- // UPDATE: Keys are equal => new value is overwriting old value.
- oldIndex += 2;
- newIndex += 2;
- if (oldValue !== newValue) {
- setKey = newKey;
- setValue = newValue;
- }
- }
- else if (newKey === null || oldKey !== null && oldKey < newKey) {
- // DELETE: oldKey key is missing or we did not find the oldKey in the newValue
- // (because the keyValueArray is sorted and `newKey` is found later alphabetically).
- // `"background" < "color"` so we need to delete `"background"` because it is not found in the
- // new array.
- oldIndex += 2;
- setKey = oldKey;
- }
- else {
- // CREATE: newKey's is earlier alphabetically than oldKey's (or no oldKey) => we have new key.
- // `"color" > "background"` so we need to add `color` because it is in new array but not in
- // old array.
- ngDevMode && assertDefined(newKey, 'Expecting to have a valid key');
- newIndex += 2;
- setKey = newKey;
- setValue = newValue;
- }
- if (setKey !== null) {
- updateStyling(tView, tNode, lView, renderer, setKey, setValue, isClassBased, bindingIndex);
- }
- oldKey = oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex] : null;
- newKey = newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex] : null;
- }
- }
- /**
- * Update a simple (property name) styling.
- *
- * This function takes `prop` and updates the DOM to that value. The function takes the binding
- * value as well as binding priority into consideration to determine which value should be written
- * to DOM. (For example it may be determined that there is a higher priority overwrite which blocks
- * the DOM write, or if the value goes to `undefined` a lower priority overwrite may be consulted.)
- *
- * @param tView Associated `TView.data` contains the linked list of binding priorities.
- * @param tNode `TNode` where the binding is located.
- * @param lView `LView` contains the values associated with other styling binding at this `TNode`.
- * @param renderer Renderer to use if any updates.
- * @param prop Either style property name or a class name.
- * @param value Either style value for `prop` or `true`/`false` if `prop` is class.
- * @param isClassBased `true` if `class` (`false` if `style`)
- * @param bindingIndex Binding index of the binding.
- */
- function updateStyling(tView, tNode, lView, renderer, prop, value, isClassBased, bindingIndex) {
- if (!(tNode.type & 3 /* TNodeType.AnyRNode */)) {
- // It is possible to have styling on non-elements (such as ng-container).
- // This is rare, but it does happen. In such a case, just ignore the binding.
- return;
- }
- const tData = tView.data;
- const tRange = tData[bindingIndex + 1];
- const higherPriorityValue = getTStylingRangeNextDuplicate(tRange) ?
- findStylingValue(tData, tNode, lView, prop, getTStylingRangeNext(tRange), isClassBased) :
- undefined;
- if (!isStylingValuePresent(higherPriorityValue)) {
- // We don't have a next duplicate, or we did not find a duplicate value.
- if (!isStylingValuePresent(value)) {
- // We should delete current value or restore to lower priority value.
- if (getTStylingRangePrevDuplicate(tRange)) {
- // We have a possible prev duplicate, let's retrieve it.
- value = findStylingValue(tData, null, lView, prop, bindingIndex, isClassBased);
- }
- }
- const rNode = getNativeByIndex(getSelectedIndex(), lView);
- applyStyling(renderer, isClassBased, rNode, prop, value);
- }
- }
- /**
- * Search for styling value with higher priority which is overwriting current value, or a
- * value of lower priority to which we should fall back if the value is `undefined`.
- *
- * When value is being applied at a location, related values need to be consulted.
- * - If there is a higher priority binding, we should be using that one instead.
- * For example `<div [style]="{color:exp1}" [style.color]="exp2">` change to `exp1`
- * requires that we check `exp2` to see if it is set to value other than `undefined`.
- * - If there is a lower priority binding and we are changing to `undefined`
- * For example `<div [style]="{color:exp1}" [style.color]="exp2">` change to `exp2` to
- * `undefined` requires that we check `exp1` (and static values) and use that as new value.
- *
- * NOTE: The styling stores two values.
- * 1. The raw value which came from the application is stored at `index + 0` location. (This value
- * is used for dirty checking).
- * 2. The normalized value is stored at `index + 1`.
- *
- * @param tData `TData` used for traversing the priority.
- * @param tNode `TNode` to use for resolving static styling. Also controls search direction.
- * - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true.
- * If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value.
- * - `null` search prev and go all the way to end. Return last value where
- * `isStylingValuePresent(value)` is true.
- * @param lView `LView` used for retrieving the actual values.
- * @param prop Property which we are interested in.
- * @param index Starting index in the linked list of styling bindings where the search should start.
- * @param isClassBased `true` if `class` (`false` if `style`)
- */
- function findStylingValue(tData, tNode, lView, prop, index, isClassBased) {
- // `TNode` to use for resolving static styling. Also controls search direction.
- // - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true.
- // If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value.
- // - `null` search prev and go all the way to end. Return last value where
- // `isStylingValuePresent(value)` is true.
- const isPrevDirection = tNode === null;
- let value = undefined;
- while (index > 0) {
- const rawKey = tData[index];
- const containsStatics = Array.isArray(rawKey);
- // Unwrap the key if we contain static values.
- const key = containsStatics ? rawKey[1] : rawKey;
- const isStylingMap = key === null;
- let valueAtLViewIndex = lView[index + 1];
- if (valueAtLViewIndex === NO_CHANGE) {
- // In firstUpdatePass the styling instructions create a linked list of styling.
- // On subsequent passes it is possible for a styling instruction to try to read a binding
- // which
- // has not yet executed. In that case we will find `NO_CHANGE` and we should assume that
- // we have `undefined` (or empty array in case of styling-map instruction) instead. This
- // allows the resolution to apply the value (which may later be overwritten when the
- // binding actually executes.)
- valueAtLViewIndex = isStylingMap ? EMPTY_ARRAY : undefined;
- }
- let currentValue = isStylingMap ? keyValueArrayGet(valueAtLViewIndex, prop) :
- (key === prop ? valueAtLViewIndex : undefined);
- if (containsStatics && !isStylingValuePresent(currentValue)) {
- currentValue = keyValueArrayGet(rawKey, prop);
- }
- if (isStylingValuePresent(currentValue)) {
- value = currentValue;
- if (isPrevDirection) {
- return value;
- }
- }
- const tRange = tData[index + 1];
- index = isPrevDirection ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange);
- }
- if (tNode !== null) {
- // in case where we are going in next direction AND we did not find anything, we need to
- // consult residual styling
- let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles;
- if (residual != null /** OR residual !=== undefined */) {
- value = keyValueArrayGet(residual, prop);
- }
- }
- return value;
- }
- /**
- * Determines if the binding value should be used (or if the value is 'undefined' and hence priority
- * resolution should be used.)
- *
- * @param value Binding style value.
- */
- function isStylingValuePresent(value) {
- // Currently only `undefined` value is considered non-binding. That is `undefined` says I don't
- // have an opinion as to what this binding should be and you should consult other bindings by
- // priority to determine the valid value.
- // This is extracted into a single function so that we have a single place to control this.
- return value !== undefined;
- }
- /**
- * Normalizes and/or adds a suffix to the value.
- *
- * If value is `null`/`undefined` no suffix is added
- * @param value
- * @param suffix
- */
- function normalizeSuffix(value, suffix) {
- if (value == null || value === '') {
- // do nothing
- // Do not add the suffix if the value is going to be empty.
- // As it produce invalid CSS, which the browsers will automatically omit but Domino will not.
- // Example: `"left": "px;"` instead of `"left": ""`.
- }
- else if (typeof suffix === 'string') {
- value = value + suffix;
- }
- else if (typeof value === 'object') {
- value = stringify(unwrapSafeValue(value));
- }
- return value;
- }
- /**
- * Tests if the `TNode` has input shadow.
- *
- * An input shadow is when a directive steals (shadows) the input by using `@Input('style')` or
- * `@Input('class')` as input.
- *
- * @param tNode `TNode` which we would like to see if it has shadow.
- * @param isClassBased `true` if `class` (`false` if `style`)
- */
- function hasStylingInputShadow(tNode, isClassBased) {
- return (tNode.flags & (isClassBased ? 8 /* TNodeFlags.hasClassInput */ : 16 /* TNodeFlags.hasStyleInput */)) !== 0;
- }
- /**
- * Create static text node
- *
- * @param index Index of the node in the data array
- * @param value Static string value to write.
- *
- * @codeGenApi
- */
- function ɵɵtext(index, value = '') {
- const lView = getLView();
- const tView = getTView();
- const adjustedIndex = index + HEADER_OFFSET;
- ngDevMode &&
- assertEqual(getBindingIndex(), tView.bindingStartIndex, 'text nodes should be created before any bindings');
- ngDevMode && assertIndexInRange(lView, adjustedIndex);
- const tNode = tView.firstCreatePass ?
- getOrCreateTNode(tView, adjustedIndex, 1 /* TNodeType.Text */, value, null) :
- tView.data[adjustedIndex];
- const textNative = _locateOrCreateTextNode(tView, lView, tNode, value, index);
- lView[adjustedIndex] = textNative;
- if (wasLastNodeCreated()) {
- appendChild(tView, lView, textNative, tNode);
- }
- // Text nodes are self closing.
- setCurrentTNode(tNode, false);
- }
- let _locateOrCreateTextNode = (tView, lView, tNode, value, index) => {
- lastNodeWasCreated(true);
- return createTextNode(lView[RENDERER], value);
- };
- /**
- * Enables hydration code path (to lookup existing elements in DOM)
- * in addition to the regular creation mode of text nodes.
- */
- function locateOrCreateTextNodeImpl(tView, lView, tNode, value, index) {
- const hydrationInfo = lView[HYDRATION];
- const isNodeCreationMode = !hydrationInfo || isInSkipHydrationBlock$1() || isDisconnectedNode(hydrationInfo, index);
- lastNodeWasCreated(isNodeCreationMode);
- // Regular creation mode.
- if (isNodeCreationMode) {
- return createTextNode(lView[RENDERER], value);
- }
- // Hydration mode, looking up an existing element in DOM.
- const textNative = locateNextRNode(hydrationInfo, tView, lView, tNode);
- ngDevMode && validateMatchingNode(textNative, Node.TEXT_NODE, null, lView, tNode);
- ngDevMode && markRNodeAsClaimedByHydration(textNative);
- return textNative;
- }
- function enableLocateOrCreateTextNodeImpl() {
- _locateOrCreateTextNode = locateOrCreateTextNodeImpl;
- }
- /**
- *
- * Update text content with a lone bound value
- *
- * Used when a text node has 1 interpolated value in it, an no additional text
- * surrounds that interpolated value:
- *
- * ```html
- * <div>{{v0}}</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate(v0);
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate(v0) {
- ɵɵtextInterpolate1('', v0, '');
- return ɵɵtextInterpolate;
- }
- /**
- *
- * Update text content with single bound value surrounded by other text.
- *
- * Used when a text node has 1 interpolated value in it:
- *
- * ```html
- * <div>prefix{{v0}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate1('prefix', v0, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate1(prefix, v0, suffix) {
- const lView = getLView();
- const interpolated = interpolation1(lView, prefix, v0, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate1;
- }
- /**
- *
- * Update text content with 2 bound values surrounded by other text.
- *
- * Used when a text node has 2 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate2('prefix', v0, '-', v1, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate2(prefix, v0, i0, v1, suffix) {
- const lView = getLView();
- const interpolated = interpolation2(lView, prefix, v0, i0, v1, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate2;
- }
- /**
- *
- * Update text content with 3 bound values surrounded by other text.
- *
- * Used when a text node has 3 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate3(
- * 'prefix', v0, '-', v1, '-', v2, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate3(prefix, v0, i0, v1, i1, v2, suffix) {
- const lView = getLView();
- const interpolated = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate3;
- }
- /**
- *
- * Update text content with 4 bound values surrounded by other text.
- *
- * Used when a text node has 4 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate4(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see ɵɵtextInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix) {
- const lView = getLView();
- const interpolated = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate4;
- }
- /**
- *
- * Update text content with 5 bound values surrounded by other text.
- *
- * Used when a text node has 5 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate5(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) {
- const lView = getLView();
- const interpolated = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate5;
- }
- /**
- *
- * Update text content with 6 bound values surrounded by other text.
- *
- * Used when a text node has 6 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate6(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix');
- * ```
- *
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change. @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) {
- const lView = getLView();
- const interpolated = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate6;
- }
- /**
- *
- * Update text content with 7 bound values surrounded by other text.
- *
- * Used when a text node has 7 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate7(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) {
- const lView = getLView();
- const interpolated = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate7;
- }
- /**
- *
- * Update text content with 8 bound values surrounded by other text.
- *
- * Used when a text node has 8 interpolated values in it:
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolate8(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix');
- * ```
- * @returns itself, so that it may be chained.
- * @see textInterpolateV
- * @codeGenApi
- */
- function ɵɵtextInterpolate8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) {
- const lView = getLView();
- const interpolated = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolate8;
- }
- /**
- * Update text content with 9 or more bound values other surrounded by text.
- *
- * Used when the number of interpolated values exceeds 8.
- *
- * ```html
- * <div>prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix</div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵtextInterpolateV(
- * ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9,
- * 'suffix']);
- * ```
- *.
- * @param values The collection of values and the strings in between those values, beginning with
- * a string prefix and ending with a string suffix.
- * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
- *
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵtextInterpolateV(values) {
- const lView = getLView();
- const interpolated = interpolationV(lView, values);
- if (interpolated !== NO_CHANGE) {
- textBindingInternal(lView, getSelectedIndex(), interpolated);
- }
- return ɵɵtextInterpolateV;
- }
- /**
- *
- * Update an interpolated class on an element with single bound value surrounded by text.
- *
- * Used when the value passed to a property has 1 interpolated value in it:
- *
- * ```html
- * <div class="prefix{{v0}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate1('prefix', v0, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate1(prefix, v0, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 2 bound values surrounded by text.
- *
- * Used when the value passed to a property has 2 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate2('prefix', v0, '-', v1, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate2(prefix, v0, i0, v1, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 3 bound values surrounded by text.
- *
- * Used when the value passed to a property has 3 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}-{{v2}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate3(
- * 'prefix', v0, '-', v1, '-', v2, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate3(prefix, v0, i0, v1, i1, v2, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 4 bound values surrounded by text.
- *
- * Used when the value passed to a property has 4 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate4(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 5 bound values surrounded by text.
- *
- * Used when the value passed to a property has 5 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate5(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 6 bound values surrounded by text.
- *
- * Used when the value passed to a property has 6 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate6(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 7 bound values surrounded by text.
- *
- * Used when the value passed to a property has 7 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate7(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated class on an element with 8 bound values surrounded by text.
- *
- * Used when the value passed to a property has 8 interpolated values in it:
- *
- * ```html
- * <div class="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolate8(
- * 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param i6 Static value used for concatenation only.
- * @param v7 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵclassMapInterpolate8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- * Update an interpolated class on an element with 9 or more bound values surrounded by text.
- *
- * Used when the number of interpolated values exceeds 8.
- *
- * ```html
- * <div
- * class="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵclassMapInterpolateV(
- * ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9,
- * 'suffix']);
- * ```
- *.
- * @param values The collection of values and the strings in-between those values, beginning with
- * a string prefix and ending with a string suffix.
- * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
- * @codeGenApi
- */
- function ɵɵclassMapInterpolateV(values) {
- const lView = getLView();
- const interpolatedValue = interpolationV(lView, values);
- checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
- }
- /**
- *
- * Update an interpolated style on an element with single bound value surrounded by text.
- *
- * Used when the value passed to a property has 1 interpolated value in it:
- *
- * ```html
- * <div style="key: {{v0}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate1('key: ', v0, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate1(prefix, v0, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 2 bound values surrounded by text.
- *
- * Used when the value passed to a property has 2 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key1: {{v1}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate2('key: ', v0, '; key1: ', v1, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate2(prefix, v0, i0, v1, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 3 bound values surrounded by text.
- *
- * Used when the value passed to a property has 3 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key2: {{v1}}; key2: {{v2}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate3(
- * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate3(prefix, v0, i0, v1, i1, v2, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 4 bound values surrounded by text.
- *
- * Used when the value passed to a property has 4 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key1: {{v1}}; key2: {{v2}}; key3: {{v3}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate4(
- * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate4(prefix, v0, i0, v1, i1, v2, i2, v3, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 5 bound values surrounded by text.
- *
- * Used when the value passed to a property has 5 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key1: {{v1}}; key2: {{v2}}; key3: {{v3}}; key4: {{v4}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate5(
- * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate5(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 6 bound values surrounded by text.
- *
- * Used when the value passed to a property has 6 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key1: {{v1}}; key2: {{v2}}; key3: {{v3}}; key4: {{v4}};
- * key5: {{v5}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate6(
- * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5,
- * 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate6(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 7 bound values surrounded by text.
- *
- * Used when the value passed to a property has 7 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key1: {{v1}}; key2: {{v2}}; key3: {{v3}}; key4: {{v4}}; key5: {{v5}};
- * key6: {{v6}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate7(
- * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5,
- * '; key6: ', v6, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate7(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style on an element with 8 bound values surrounded by text.
- *
- * Used when the value passed to a property has 8 interpolated values in it:
- *
- * ```html
- * <div style="key: {{v0}}; key1: {{v1}}; key2: {{v2}}; key3: {{v3}}; key4: {{v4}}; key5: {{v5}};
- * key6: {{v6}}; key7: {{v7}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolate8(
- * 'key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5,
- * '; key6: ', v6, '; key7: ', v7, 'suffix');
- * ```
- *
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param i6 Static value used for concatenation only.
- * @param v7 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolate8(prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix) {
- const lView = getLView();
- const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- * Update an interpolated style on an element with 9 or more bound values surrounded by text.
- *
- * Used when the number of interpolated values exceeds 8.
- *
- * ```html
- * <div
- * class="key: {{v0}}; key1: {{v1}}; key2: {{v2}}; key3: {{v3}}; key4: {{v4}}; key5: {{v5}};
- * key6: {{v6}}; key7: {{v7}}; key8: {{v8}}; key9: {{v9}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstyleMapInterpolateV(
- * ['key: ', v0, '; key1: ', v1, '; key2: ', v2, '; key3: ', v3, '; key4: ', v4, '; key5: ', v5,
- * '; key6: ', v6, '; key7: ', v7, '; key8: ', v8, '; key9: ', v9, 'suffix']);
- * ```
- *.
- * @param values The collection of values and the strings in-between those values, beginning with
- * a string prefix and ending with a string suffix.
- * (e.g. `['prefix', value0, '; key2: ', value1, '; key2: ', value2, ..., value99, 'suffix']`)
- * @codeGenApi
- */
- function ɵɵstyleMapInterpolateV(values) {
- const lView = getLView();
- const interpolatedValue = interpolationV(lView, values);
- ɵɵstyleMap(interpolatedValue);
- }
- /**
- *
- * Update an interpolated style property on an element with single bound value surrounded by text.
- *
- * Used when the value passed to a property has 1 interpolated value in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate1(0, 'prefix', v0, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate1(prop, prefix, v0, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate1;
- }
- /**
- *
- * Update an interpolated style property on an element with 2 bound values surrounded by text.
- *
- * Used when the value passed to a property has 2 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate2(0, 'prefix', v0, '-', v1, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate2(prop, prefix, v0, i0, v1, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate2;
- }
- /**
- *
- * Update an interpolated style property on an element with 3 bound values surrounded by text.
- *
- * Used when the value passed to a property has 3 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}-{{v2}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate3(0, 'prefix', v0, '-', v1, '-', v2, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate3(prop, prefix, v0, i0, v1, i1, v2, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate3;
- }
- /**
- *
- * Update an interpolated style property on an element with 4 bound values surrounded by text.
- *
- * Used when the value passed to a property has 4 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate4(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate4(prop, prefix, v0, i0, v1, i1, v2, i2, v3, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate4;
- }
- /**
- *
- * Update an interpolated style property on an element with 5 bound values surrounded by text.
- *
- * Used when the value passed to a property has 5 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate5(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate5(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate5;
- }
- /**
- *
- * Update an interpolated style property on an element with 6 bound values surrounded by text.
- *
- * Used when the value passed to a property has 6 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate6(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate6(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate6;
- }
- /**
- *
- * Update an interpolated style property on an element with 7 bound values surrounded by text.
- *
- * Used when the value passed to a property has 7 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate7(
- * 0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate7(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate7;
- }
- /**
- *
- * Update an interpolated style property on an element with 8 bound values surrounded by text.
- *
- * Used when the value passed to a property has 8 interpolated values in it:
- *
- * ```html
- * <div style.color="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}suffix"></div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolate8(0, 'prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6,
- * '-', v7, 'suffix');
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`.
- * @param prefix Static value used for concatenation only.
- * @param v0 Value checked for change.
- * @param i0 Static value used for concatenation only.
- * @param v1 Value checked for change.
- * @param i1 Static value used for concatenation only.
- * @param v2 Value checked for change.
- * @param i2 Static value used for concatenation only.
- * @param v3 Value checked for change.
- * @param i3 Static value used for concatenation only.
- * @param v4 Value checked for change.
- * @param i4 Static value used for concatenation only.
- * @param v5 Value checked for change.
- * @param i5 Static value used for concatenation only.
- * @param v6 Value checked for change.
- * @param i6 Static value used for concatenation only.
- * @param v7 Value checked for change.
- * @param suffix Static value used for concatenation only.
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolate8(prop, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolation8(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolate8;
- }
- /**
- * Update an interpolated style property on an element with 9 or more bound values surrounded by
- * text.
- *
- * Used when the number of interpolated values exceeds 8.
- *
- * ```html
- * <div
- * style.color="prefix{{v0}}-{{v1}}-{{v2}}-{{v3}}-{{v4}}-{{v5}}-{{v6}}-{{v7}}-{{v8}}-{{v9}}suffix">
- * </div>
- * ```
- *
- * Its compiled representation is:
- *
- * ```ts
- * ɵɵstylePropInterpolateV(
- * 0, ['prefix', v0, '-', v1, '-', v2, '-', v3, '-', v4, '-', v5, '-', v6, '-', v7, '-', v9,
- * 'suffix']);
- * ```
- *
- * @param styleIndex Index of style to update. This index value refers to the
- * index of the style in the style bindings array that was passed into
- * `styling`..
- * @param values The collection of values and the strings in-between those values, beginning with
- * a string prefix and ending with a string suffix.
- * (e.g. `['prefix', value0, '-', value1, '-', value2, ..., value99, 'suffix']`)
- * @param valueSuffix Optional suffix. Used with scalar values to add unit such as `px`.
- * @returns itself, so that it may be chained.
- * @codeGenApi
- */
- function ɵɵstylePropInterpolateV(prop, values, valueSuffix) {
- const lView = getLView();
- const interpolatedValue = interpolationV(lView, values);
- checkStylingProperty(prop, interpolatedValue, valueSuffix, false);
- return ɵɵstylePropInterpolateV;
- }
- /**
- * Update a property on a host element. Only applies to native node properties, not inputs.
- *
- * Operates on the element selected by index via the {@link select} instruction.
- *
- * @param propName Name of property. Because it is going to DOM, this is not subject to
- * renaming as part of minification.
- * @param value New value to write.
- * @param sanitizer An optional function used to sanitize the value.
- * @returns This function returns itself so that it may be chained
- * (e.g. `property('name', ctx.name)('title', ctx.title)`)
- *
- * @codeGenApi
- */
- function ɵɵhostProperty(propName, value, sanitizer) {
- const lView = getLView();
- const bindingIndex = nextBindingIndex();
- if (bindingUpdated(lView, bindingIndex, value)) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- elementPropertyInternal(tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, true);
- ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
- }
- return ɵɵhostProperty;
- }
- /**
- * Updates a synthetic host binding (e.g. `[@foo]`) on a component or directive.
- *
- * This instruction is for compatibility purposes and is designed to ensure that a
- * synthetic host binding (e.g. `@HostBinding('@foo')`) properly gets rendered in
- * the component's renderer. Normally all host bindings are evaluated with the parent
- * component's renderer, but, in the case of animation @triggers, they need to be
- * evaluated with the sub component's renderer (because that's where the animation
- * triggers are defined).
- *
- * Do not use this instruction as a replacement for `elementProperty`. This instruction
- * only exists to ensure compatibility with the ViewEngine's host binding behavior.
- *
- * @param index The index of the element to update in the data array
- * @param propName Name of property. Because it is going to DOM, this is not subject to
- * renaming as part of minification.
- * @param value New value to write.
- * @param sanitizer An optional function used to sanitize the value.
- *
- * @codeGenApi
- */
- function ɵɵsyntheticHostProperty(propName, value, sanitizer) {
- const lView = getLView();
- const bindingIndex = nextBindingIndex();
- if (bindingUpdated(lView, bindingIndex, value)) {
- const tView = getTView();
- const tNode = getSelectedTNode();
- const currentDef = getCurrentDirectiveDef(tView.data);
- const renderer = loadComponentRenderer(currentDef, tNode, lView);
- elementPropertyInternal(tView, tNode, lView, propName, value, renderer, sanitizer, true);
- ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
- }
- return ɵɵsyntheticHostProperty;
- }
- /**
- * NOTE: changes to the `ngI18nClosureMode` name must be synced with `compiler-cli/src/tooling.ts`.
- */
- if (typeof ngI18nClosureMode === 'undefined') {
- // These property accesses can be ignored because ngI18nClosureMode will be set to false
- // when optimizing code and the whole if statement will be dropped.
- // Make sure to refer to ngI18nClosureMode as ['ngI18nClosureMode'] for closure.
- // NOTE: we need to have it in IIFE so that the tree-shaker is happy.
- (function () {
- // tslint:disable-next-line:no-toplevel-property-access
- _global['ngI18nClosureMode'] =
- // TODO(FW-1250): validate that this actually, you know, works.
- // tslint:disable-next-line:no-toplevel-property-access
- typeof goog !== 'undefined' && typeof goog.getMsg === 'function';
- })();
- }
- // THIS CODE IS GENERATED - DO NOT MODIFY.
- const u = undefined;
- function plural(val) {
- const n = val, i = Math.floor(Math.abs(val)), v = val.toString().replace(/^[^.]*\.?/, '').length;
- if (i === 1 && v === 0)
- return 1;
- return 5;
- }
- var localeEn = ["en", [["a", "p"], ["AM", "PM"], u], [["AM", "PM"], u, u], [["S", "M", "T", "W", "T", "F", "S"], ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]], u, [["J", "F", "M", "A", "M", "J", "J", "A", "S", "O", "N", "D"], ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]], u, [["B", "A"], ["BC", "AD"], ["Before Christ", "Anno Domini"]], 0, [6, 0], ["M/d/yy", "MMM d, y", "MMMM d, y", "EEEE, MMMM d, y"], ["h:mm a", "h:mm:ss a", "h:mm:ss a z", "h:mm:ss a zzzz"], ["{1}, {0}", u, "{1} 'at' {0}", u], [".", ",", ";", "%", "+", "-", "E", "×", "‰", "∞", "NaN", ":"], ["#,##0.###", "#,##0%", "¤#,##0.00", "#E0"], "USD", "$", "US Dollar", {}, "ltr", plural];
- /**
- * This const is used to store the locale data registered with `registerLocaleData`
- */
- let LOCALE_DATA = {};
- /**
- * Register locale data to be used internally by Angular. See the
- * ["I18n guide"](guide/i18n-common-format-data-locale) to know how to import additional locale
- * data.
- *
- * The signature `registerLocaleData(data: any, extraData?: any)` is deprecated since v5.1
- */
- function registerLocaleData(data, localeId, extraData) {
- if (typeof localeId !== 'string') {
- extraData = localeId;
- localeId = data[LocaleDataIndex.LocaleId];
- }
- localeId = localeId.toLowerCase().replace(/_/g, '-');
- LOCALE_DATA[localeId] = data;
- if (extraData) {
- LOCALE_DATA[localeId][LocaleDataIndex.ExtraData] = extraData;
- }
- }
- /**
- * Finds the locale data for a given locale.
- *
- * @param locale The locale code.
- * @returns The locale data.
- * @see [Internationalization (i18n) Guide](https://angular.io/guide/i18n-overview)
- */
- function findLocaleData(locale) {
- const normalizedLocale = normalizeLocale(locale);
- let match = getLocaleData(normalizedLocale);
- if (match) {
- return match;
- }
- // let's try to find a parent locale
- const parentLocale = normalizedLocale.split('-')[0];
- match = getLocaleData(parentLocale);
- if (match) {
- return match;
- }
- if (parentLocale === 'en') {
- return localeEn;
- }
- throw new RuntimeError(701 /* RuntimeErrorCode.MISSING_LOCALE_DATA */, ngDevMode && `Missing locale data for the locale "${locale}".`);
- }
- /**
- * Retrieves the default currency code for the given locale.
- *
- * The default is defined as the first currency which is still in use.
- *
- * @param locale The code of the locale whose currency code we want.
- * @returns The code of the default currency for the given locale.
- *
- */
- function getLocaleCurrencyCode(locale) {
- const data = findLocaleData(locale);
- return data[LocaleDataIndex.CurrencyCode] || null;
- }
- /**
- * Retrieves the plural function used by ICU expressions to determine the plural case to use
- * for a given locale.
- * @param locale A locale code for the locale format rules to use.
- * @returns The plural function for the locale.
- * @see {@link NgPlural}
- * @see [Internationalization (i18n) Guide](/guide/i18n-overview)
- */
- function getLocalePluralCase(locale) {
- const data = findLocaleData(locale);
- return data[LocaleDataIndex.PluralCase];
- }
- /**
- * Helper function to get the given `normalizedLocale` from `LOCALE_DATA`
- * or from the global `ng.common.locale`.
- */
- function getLocaleData(normalizedLocale) {
- if (!(normalizedLocale in LOCALE_DATA)) {
- LOCALE_DATA[normalizedLocale] = _global.ng && _global.ng.common && _global.ng.common.locales &&
- _global.ng.common.locales[normalizedLocale];
- }
- return LOCALE_DATA[normalizedLocale];
- }
- /**
- * Helper function to remove all the locale data from `LOCALE_DATA`.
- */
- function unregisterAllLocaleData() {
- LOCALE_DATA = {};
- }
- /**
- * Index of each type of locale data from the locale data array
- */
- var LocaleDataIndex;
- (function (LocaleDataIndex) {
- LocaleDataIndex[LocaleDataIndex["LocaleId"] = 0] = "LocaleId";
- LocaleDataIndex[LocaleDataIndex["DayPeriodsFormat"] = 1] = "DayPeriodsFormat";
- LocaleDataIndex[LocaleDataIndex["DayPeriodsStandalone"] = 2] = "DayPeriodsStandalone";
- LocaleDataIndex[LocaleDataIndex["DaysFormat"] = 3] = "DaysFormat";
- LocaleDataIndex[LocaleDataIndex["DaysStandalone"] = 4] = "DaysStandalone";
- LocaleDataIndex[LocaleDataIndex["MonthsFormat"] = 5] = "MonthsFormat";
- LocaleDataIndex[LocaleDataIndex["MonthsStandalone"] = 6] = "MonthsStandalone";
- LocaleDataIndex[LocaleDataIndex["Eras"] = 7] = "Eras";
- LocaleDataIndex[LocaleDataIndex["FirstDayOfWeek"] = 8] = "FirstDayOfWeek";
- LocaleDataIndex[LocaleDataIndex["WeekendRange"] = 9] = "WeekendRange";
- LocaleDataIndex[LocaleDataIndex["DateFormat"] = 10] = "DateFormat";
- LocaleDataIndex[LocaleDataIndex["TimeFormat"] = 11] = "TimeFormat";
- LocaleDataIndex[LocaleDataIndex["DateTimeFormat"] = 12] = "DateTimeFormat";
- LocaleDataIndex[LocaleDataIndex["NumberSymbols"] = 13] = "NumberSymbols";
- LocaleDataIndex[LocaleDataIndex["NumberFormats"] = 14] = "NumberFormats";
- LocaleDataIndex[LocaleDataIndex["CurrencyCode"] = 15] = "CurrencyCode";
- LocaleDataIndex[LocaleDataIndex["CurrencySymbol"] = 16] = "CurrencySymbol";
- LocaleDataIndex[LocaleDataIndex["CurrencyName"] = 17] = "CurrencyName";
- LocaleDataIndex[LocaleDataIndex["Currencies"] = 18] = "Currencies";
- LocaleDataIndex[LocaleDataIndex["Directionality"] = 19] = "Directionality";
- LocaleDataIndex[LocaleDataIndex["PluralCase"] = 20] = "PluralCase";
- LocaleDataIndex[LocaleDataIndex["ExtraData"] = 21] = "ExtraData";
- })(LocaleDataIndex || (LocaleDataIndex = {}));
- /**
- * Returns the canonical form of a locale name - lowercase with `_` replaced with `-`.
- */
- function normalizeLocale(locale) {
- return locale.toLowerCase().replace(/_/g, '-');
- }
- const pluralMapping = ['zero', 'one', 'two', 'few', 'many'];
- /**
- * Returns the plural case based on the locale
- */
- function getPluralCase(value, locale) {
- const plural = getLocalePluralCase(locale)(parseInt(value, 10));
- const result = pluralMapping[plural];
- return (result !== undefined) ? result : 'other';
- }
- /**
- * The locale id that the application is using by default (for translations and ICU expressions).
- */
- const DEFAULT_LOCALE_ID = 'en-US';
- /**
- * USD currency code that the application uses by default for CurrencyPipe when no
- * DEFAULT_CURRENCY_CODE is provided.
- */
- const USD_CURRENCY_CODE = 'USD';
- /**
- * Marks that the next string is an element name.
- *
- * See `I18nMutateOpCodes` documentation.
- */
- const ELEMENT_MARKER = {
- marker: 'element'
- };
- /**
- * Marks that the next string is comment text need for ICU.
- *
- * See `I18nMutateOpCodes` documentation.
- */
- const ICU_MARKER = {
- marker: 'ICU'
- };
- /**
- * See `I18nCreateOpCodes`
- */
- var I18nCreateOpCode;
- (function (I18nCreateOpCode) {
- /**
- * Number of bits to shift index so that it can be combined with the `APPEND_EAGERLY` and
- * `COMMENT`.
- */
- I18nCreateOpCode[I18nCreateOpCode["SHIFT"] = 2] = "SHIFT";
- /**
- * Should the node be appended to parent immediately after creation.
- */
- I18nCreateOpCode[I18nCreateOpCode["APPEND_EAGERLY"] = 1] = "APPEND_EAGERLY";
- /**
- * If set the node should be comment (rather than a text) node.
- */
- I18nCreateOpCode[I18nCreateOpCode["COMMENT"] = 2] = "COMMENT";
- })(I18nCreateOpCode || (I18nCreateOpCode = {}));
- // Note: This hack is necessary so we don't erroneously get a circular dependency
- // failure based on types.
- const unusedValueExportToPlacateAjd = 1;
- /**
- * The locale id that the application is currently using (for translations and ICU expressions).
- * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine
- * but is now defined as a global value.
- */
- let LOCALE_ID = DEFAULT_LOCALE_ID;
- /**
- * Sets the locale id that will be used for translations and ICU expressions.
- * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine
- * but is now defined as a global value.
- *
- * @param localeId
- */
- function setLocaleId(localeId) {
- assertDefined(localeId, `Expected localeId to be defined`);
- if (typeof localeId === 'string') {
- LOCALE_ID = localeId.toLowerCase().replace(/_/g, '-');
- }
- }
- /**
- * Gets the locale id that will be used for translations and ICU expressions.
- * This is the ivy version of `LOCALE_ID` that was defined as an injection token for the view engine
- * but is now defined as a global value.
- */
- function getLocaleId() {
- return LOCALE_ID;
- }
- /**
- * Find a node in front of which `currentTNode` should be inserted (takes i18n into account).
- *
- * This method determines the `RNode` in front of which we should insert the `currentRNode`. This
- * takes `TNode.insertBeforeIndex` into account.
- *
- * @param parentTNode parent `TNode`
- * @param currentTNode current `TNode` (The node which we would like to insert into the DOM)
- * @param lView current `LView`
- */
- function getInsertInFrontOfRNodeWithI18n(parentTNode, currentTNode, lView) {
- const tNodeInsertBeforeIndex = currentTNode.insertBeforeIndex;
- const insertBeforeIndex = Array.isArray(tNodeInsertBeforeIndex) ? tNodeInsertBeforeIndex[0] : tNodeInsertBeforeIndex;
- if (insertBeforeIndex === null) {
- return getInsertInFrontOfRNodeWithNoI18n(parentTNode, currentTNode, lView);
- }
- else {
- ngDevMode && assertIndexInRange(lView, insertBeforeIndex);
- return unwrapRNode(lView[insertBeforeIndex]);
- }
- }
- /**
- * Process `TNode.insertBeforeIndex` by adding i18n text nodes.
- *
- * See `TNode.insertBeforeIndex`
- */
- function processI18nInsertBefore(renderer, childTNode, lView, childRNode, parentRElement) {
- const tNodeInsertBeforeIndex = childTNode.insertBeforeIndex;
- if (Array.isArray(tNodeInsertBeforeIndex)) {
- // An array indicates that there are i18n nodes that need to be added as children of this
- // `childRNode`. These i18n nodes were created before this `childRNode` was available and so
- // only now can be added. The first element of the array is the normal index where we should
- // insert the `childRNode`. Additional elements are the extra nodes to be added as children of
- // `childRNode`.
- ngDevMode && assertDomNode(childRNode);
- let i18nParent = childRNode;
- let anchorRNode = null;
- if (!(childTNode.type & 3 /* TNodeType.AnyRNode */)) {
- anchorRNode = i18nParent;
- i18nParent = parentRElement;
- }
- if (i18nParent !== null && childTNode.componentOffset === -1) {
- for (let i = 1; i < tNodeInsertBeforeIndex.length; i++) {
- // No need to `unwrapRNode` because all of the indexes point to i18n text nodes.
- // see `assertDomNode` below.
- const i18nChild = lView[tNodeInsertBeforeIndex[i]];
- nativeInsertBefore(renderer, i18nParent, i18nChild, anchorRNode, false);
- }
- }
- }
- }
- /**
- * Add `tNode` to `previousTNodes` list and update relevant `TNode`s in `previousTNodes` list
- * `tNode.insertBeforeIndex`.
- *
- * Things to keep in mind:
- * 1. All i18n text nodes are encoded as `TNodeType.Element` and are created eagerly by the
- * `ɵɵi18nStart` instruction.
- * 2. All `TNodeType.Placeholder` `TNodes` are elements which will be created later by
- * `ɵɵelementStart` instruction.
- * 3. `ɵɵelementStart` instruction will create `TNode`s in the ascending `TNode.index` order. (So a
- * smaller index `TNode` is guaranteed to be created before a larger one)
- *
- * We use the above three invariants to determine `TNode.insertBeforeIndex`.
- *
- * In an ideal world `TNode.insertBeforeIndex` would always be `TNode.next.index`. However,
- * this will not work because `TNode.next.index` may be larger than `TNode.index` which means that
- * the next node is not yet created and therefore we can't insert in front of it.
- *
- * Rule1: `TNode.insertBeforeIndex = null` if `TNode.next === null` (Initial condition, as we don't
- * know if there will be further `TNode`s inserted after.)
- * Rule2: If `previousTNode` is created after the `tNode` being inserted, then
- * `previousTNode.insertBeforeNode = tNode.index` (So when a new `tNode` is added we check
- * previous to see if we can update its `insertBeforeTNode`)
- *
- * See `TNode.insertBeforeIndex` for more context.
- *
- * @param previousTNodes A list of previous TNodes so that we can easily traverse `TNode`s in
- * reverse order. (If `TNode` would have `previous` this would not be necessary.)
- * @param newTNode A TNode to add to the `previousTNodes` list.
- */
- function addTNodeAndUpdateInsertBeforeIndex(previousTNodes, newTNode) {
- // Start with Rule1
- ngDevMode &&
- assertEqual(newTNode.insertBeforeIndex, null, 'We expect that insertBeforeIndex is not set');
- previousTNodes.push(newTNode);
- if (previousTNodes.length > 1) {
- for (let i = previousTNodes.length - 2; i >= 0; i--) {
- const existingTNode = previousTNodes[i];
- // Text nodes are created eagerly and so they don't need their `indexBeforeIndex` updated.
- // It is safe to ignore them.
- if (!isI18nText(existingTNode)) {
- if (isNewTNodeCreatedBefore(existingTNode, newTNode) &&
- getInsertBeforeIndex(existingTNode) === null) {
- // If it was created before us in time, (and it does not yet have `insertBeforeIndex`)
- // then add the `insertBeforeIndex`.
- setInsertBeforeIndex(existingTNode, newTNode.index);
- }
- }
- }
- }
- }
- function isI18nText(tNode) {
- return !(tNode.type & 64 /* TNodeType.Placeholder */);
- }
- function isNewTNodeCreatedBefore(existingTNode, newTNode) {
- return isI18nText(newTNode) || existingTNode.index > newTNode.index;
- }
- function getInsertBeforeIndex(tNode) {
- const index = tNode.insertBeforeIndex;
- return Array.isArray(index) ? index[0] : index;
- }
- function setInsertBeforeIndex(tNode, value) {
- const index = tNode.insertBeforeIndex;
- if (Array.isArray(index)) {
- // Array is stored if we have to insert child nodes. See `TNode.insertBeforeIndex`
- index[0] = value;
- }
- else {
- setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore);
- tNode.insertBeforeIndex = value;
- }
- }
- /**
- * Retrieve `TIcu` at a given `index`.
- *
- * The `TIcu` can be stored either directly (if it is nested ICU) OR
- * it is stored inside tho `TIcuContainer` if it is top level ICU.
- *
- * The reason for this is that the top level ICU need a `TNode` so that they are part of the render
- * tree, but nested ICU's have no TNode, because we don't know ahead of time if the nested ICU is
- * expressed (parent ICU may have selected a case which does not contain it.)
- *
- * @param tView Current `TView`.
- * @param index Index where the value should be read from.
- */
- function getTIcu(tView, index) {
- const value = tView.data[index];
- if (value === null || typeof value === 'string')
- return null;
- if (ngDevMode &&
- !(value.hasOwnProperty('tView') || value.hasOwnProperty('currentCaseLViewIndex'))) {
- throwError('We expect to get \'null\'|\'TIcu\'|\'TIcuContainer\', but got: ' + value);
- }
- // Here the `value.hasOwnProperty('currentCaseLViewIndex')` is a polymorphic read as it can be
- // either TIcu or TIcuContainerNode. This is not ideal, but we still think it is OK because it
- // will be just two cases which fits into the browser inline cache (inline cache can take up to
- // 4)
- const tIcu = value.hasOwnProperty('currentCaseLViewIndex') ? value :
- value.value;
- ngDevMode && assertTIcu(tIcu);
- return tIcu;
- }
- /**
- * Store `TIcu` at a give `index`.
- *
- * The `TIcu` can be stored either directly (if it is nested ICU) OR
- * it is stored inside tho `TIcuContainer` if it is top level ICU.
- *
- * The reason for this is that the top level ICU need a `TNode` so that they are part of the render
- * tree, but nested ICU's have no TNode, because we don't know ahead of time if the nested ICU is
- * expressed (parent ICU may have selected a case which does not contain it.)
- *
- * @param tView Current `TView`.
- * @param index Index where the value should be stored at in `Tview.data`
- * @param tIcu The TIcu to store.
- */
- function setTIcu(tView, index, tIcu) {
- const tNode = tView.data[index];
- ngDevMode &&
- assertEqual(tNode === null || tNode.hasOwnProperty('tView'), true, 'We expect to get \'null\'|\'TIcuContainer\'');
- if (tNode === null) {
- tView.data[index] = tIcu;
- }
- else {
- ngDevMode && assertTNodeType(tNode, 32 /* TNodeType.Icu */);
- tNode.value = tIcu;
- }
- }
- /**
- * Set `TNode.insertBeforeIndex` taking the `Array` into account.
- *
- * See `TNode.insertBeforeIndex`
- */
- function setTNodeInsertBeforeIndex(tNode, index) {
- ngDevMode && assertTNode(tNode);
- let insertBeforeIndex = tNode.insertBeforeIndex;
- if (insertBeforeIndex === null) {
- setI18nHandling(getInsertInFrontOfRNodeWithI18n, processI18nInsertBefore);
- insertBeforeIndex = tNode.insertBeforeIndex =
- [null /* may be updated to number later */, index];
- }
- else {
- assertEqual(Array.isArray(insertBeforeIndex), true, 'Expecting array here');
- insertBeforeIndex.push(index);
- }
- }
- /**
- * Create `TNode.type=TNodeType.Placeholder` node.
- *
- * See `TNodeType.Placeholder` for more information.
- */
- function createTNodePlaceholder(tView, previousTNodes, index) {
- const tNode = createTNodeAtIndex(tView, index, 64 /* TNodeType.Placeholder */, null, null);
- addTNodeAndUpdateInsertBeforeIndex(previousTNodes, tNode);
- return tNode;
- }
- /**
- * Returns current ICU case.
- *
- * ICU cases are stored as index into the `TIcu.cases`.
- * At times it is necessary to communicate that the ICU case just switched and that next ICU update
- * should update all bindings regardless of the mask. In such a case the we store negative numbers
- * for cases which have just been switched. This function removes the negative flag.
- */
- function getCurrentICUCaseIndex(tIcu, lView) {
- const currentCase = lView[tIcu.currentCaseLViewIndex];
- return currentCase === null ? currentCase : (currentCase < 0 ? ~currentCase : currentCase);
- }
- function getParentFromIcuCreateOpCode(mergedCode) {
- return mergedCode >>> 17 /* IcuCreateOpCode.SHIFT_PARENT */;
- }
- function getRefFromIcuCreateOpCode(mergedCode) {
- return (mergedCode & 131070 /* IcuCreateOpCode.MASK_REF */) >>> 1 /* IcuCreateOpCode.SHIFT_REF */;
- }
- function getInstructionFromIcuCreateOpCode(mergedCode) {
- return mergedCode & 1 /* IcuCreateOpCode.MASK_INSTRUCTION */;
- }
- function icuCreateOpCode(opCode, parentIdx, refIdx) {
- ngDevMode && assertGreaterThanOrEqual(parentIdx, 0, 'Missing parent index');
- ngDevMode && assertGreaterThan(refIdx, 0, 'Missing ref index');
- return opCode | parentIdx << 17 /* IcuCreateOpCode.SHIFT_PARENT */ | refIdx << 1 /* IcuCreateOpCode.SHIFT_REF */;
- }
- /**
- * Keep track of which input bindings in `ɵɵi18nExp` have changed.
- *
- * This is used to efficiently update expressions in i18n only when the corresponding input has
- * changed.
- *
- * 1) Each bit represents which of the `ɵɵi18nExp` has changed.
- * 2) There are 32 bits allowed in JS.
- * 3) Bit 32 is special as it is shared for all changes past 32. (In other words if you have more
- * than 32 `ɵɵi18nExp` then all changes past 32nd `ɵɵi18nExp` will be mapped to same bit. This means
- * that we may end up changing more than we need to. But i18n expressions with 32 bindings is rare
- * so in practice it should not be an issue.)
- */
- let changeMask = 0b0;
- /**
- * Keeps track of which bit needs to be updated in `changeMask`
- *
- * This value gets incremented on every call to `ɵɵi18nExp`
- */
- let changeMaskCounter = 0;
- /**
- * Keep track of which input bindings in `ɵɵi18nExp` have changed.
- *
- * `setMaskBit` gets invoked by each call to `ɵɵi18nExp`.
- *
- * @param hasChange did `ɵɵi18nExp` detect a change.
- */
- function setMaskBit(hasChange) {
- if (hasChange) {
- changeMask = changeMask | (1 << Math.min(changeMaskCounter, 31));
- }
- changeMaskCounter++;
- }
- function applyI18n(tView, lView, index) {
- if (changeMaskCounter > 0) {
- ngDevMode && assertDefined(tView, `tView should be defined`);
- const tI18n = tView.data[index];
- // When `index` points to an `ɵɵi18nAttributes` then we have an array otherwise `TI18n`
- const updateOpCodes = Array.isArray(tI18n) ? tI18n : tI18n.update;
- const bindingsStartIndex = getBindingIndex() - changeMaskCounter - 1;
- applyUpdateOpCodes(tView, lView, updateOpCodes, bindingsStartIndex, changeMask);
- }
- // Reset changeMask & maskBit to default for the next update cycle
- changeMask = 0b0;
- changeMaskCounter = 0;
- }
- /**
- * Apply `I18nCreateOpCodes` op-codes as stored in `TI18n.create`.
- *
- * Creates text (and comment) nodes which are internationalized.
- *
- * @param lView Current lView
- * @param createOpCodes Set of op-codes to apply
- * @param parentRNode Parent node (so that direct children can be added eagerly) or `null` if it is
- * a root node.
- * @param insertInFrontOf DOM node that should be used as an anchor.
- */
- function applyCreateOpCodes(lView, createOpCodes, parentRNode, insertInFrontOf) {
- const renderer = lView[RENDERER];
- for (let i = 0; i < createOpCodes.length; i++) {
- const opCode = createOpCodes[i++];
- const text = createOpCodes[i];
- const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT;
- const appendNow = (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY;
- const index = opCode >>> I18nCreateOpCode.SHIFT;
- let rNode = lView[index];
- if (rNode === null) {
- // We only create new DOM nodes if they don't already exist: If ICU switches case back to a
- // case which was already instantiated, no need to create new DOM nodes.
- rNode = lView[index] =
- isComment ? renderer.createComment(text) : createTextNode(renderer, text);
- }
- if (appendNow && parentRNode !== null) {
- nativeInsertBefore(renderer, parentRNode, rNode, insertInFrontOf, false);
- }
- }
- }
- /**
- * Apply `I18nMutateOpCodes` OpCodes.
- *
- * @param tView Current `TView`
- * @param mutableOpCodes Mutable OpCodes to process
- * @param lView Current `LView`
- * @param anchorRNode place where the i18n node should be inserted.
- */
- function applyMutableOpCodes(tView, mutableOpCodes, lView, anchorRNode) {
- ngDevMode && assertDomNode(anchorRNode);
- const renderer = lView[RENDERER];
- // `rootIdx` represents the node into which all inserts happen.
- let rootIdx = null;
- // `rootRNode` represents the real node into which we insert. This can be different from
- // `lView[rootIdx]` if we have projection.
- // - null we don't have a parent (as can be the case in when we are inserting into a root of
- // LView which has no parent.)
- // - `RElement` The element representing the root after taking projection into account.
- let rootRNode;
- for (let i = 0; i < mutableOpCodes.length; i++) {
- const opCode = mutableOpCodes[i];
- if (typeof opCode == 'string') {
- const textNodeIndex = mutableOpCodes[++i];
- if (lView[textNodeIndex] === null) {
- ngDevMode && ngDevMode.rendererCreateTextNode++;
- ngDevMode && assertIndexInRange(lView, textNodeIndex);
- lView[textNodeIndex] = createTextNode(renderer, opCode);
- }
- }
- else if (typeof opCode == 'number') {
- switch (opCode & 1 /* IcuCreateOpCode.MASK_INSTRUCTION */) {
- case 0 /* IcuCreateOpCode.AppendChild */:
- const parentIdx = getParentFromIcuCreateOpCode(opCode);
- if (rootIdx === null) {
- // The first operation should save the `rootIdx` because the first operation
- // must insert into the root. (Only subsequent operations can insert into a dynamic
- // parent)
- rootIdx = parentIdx;
- rootRNode = nativeParentNode(renderer, anchorRNode);
- }
- let insertInFrontOf;
- let parentRNode;
- if (parentIdx === rootIdx) {
- insertInFrontOf = anchorRNode;
- parentRNode = rootRNode;
- }
- else {
- insertInFrontOf = null;
- parentRNode = unwrapRNode(lView[parentIdx]);
- }
- // FIXME(misko): Refactor with `processI18nText`
- if (parentRNode !== null) {
- // This can happen if the `LView` we are adding to is not attached to a parent `LView`.
- // In such a case there is no "root" we can attach to. This is fine, as we still need to
- // create the elements. When the `LView` gets later added to a parent these "root" nodes
- // get picked up and added.
- ngDevMode && assertDomNode(parentRNode);
- const refIdx = getRefFromIcuCreateOpCode(opCode);
- ngDevMode && assertGreaterThan(refIdx, HEADER_OFFSET, 'Missing ref');
- // `unwrapRNode` is not needed here as all of these point to RNodes as part of the i18n
- // which can't have components.
- const child = lView[refIdx];
- ngDevMode && assertDomNode(child);
- nativeInsertBefore(renderer, parentRNode, child, insertInFrontOf, false);
- const tIcu = getTIcu(tView, refIdx);
- if (tIcu !== null && typeof tIcu === 'object') {
- // If we just added a comment node which has ICU then that ICU may have already been
- // rendered and therefore we need to re-add it here.
- ngDevMode && assertTIcu(tIcu);
- const caseIndex = getCurrentICUCaseIndex(tIcu, lView);
- if (caseIndex !== null) {
- applyMutableOpCodes(tView, tIcu.create[caseIndex], lView, lView[tIcu.anchorIdx]);
- }
- }
- }
- break;
- case 1 /* IcuCreateOpCode.Attr */:
- const elementNodeIndex = opCode >>> 1 /* IcuCreateOpCode.SHIFT_REF */;
- const attrName = mutableOpCodes[++i];
- const attrValue = mutableOpCodes[++i];
- // This code is used for ICU expressions only, since we don't support
- // directives/components in ICUs, we don't need to worry about inputs here
- setElementAttribute(renderer, getNativeByIndex(elementNodeIndex, lView), null, null, attrName, attrValue, null);
- break;
- default:
- if (ngDevMode) {
- throw new RuntimeError(700 /* RuntimeErrorCode.INVALID_I18N_STRUCTURE */, `Unable to determine the type of mutate operation for "${opCode}"`);
- }
- }
- }
- else {
- switch (opCode) {
- case ICU_MARKER:
- const commentValue = mutableOpCodes[++i];
- const commentNodeIndex = mutableOpCodes[++i];
- if (lView[commentNodeIndex] === null) {
- ngDevMode &&
- assertEqual(typeof commentValue, 'string', `Expected "${commentValue}" to be a comment node value`);
- ngDevMode && ngDevMode.rendererCreateComment++;
- ngDevMode && assertIndexInExpandoRange(lView, commentNodeIndex);
- const commentRNode = lView[commentNodeIndex] =
- createCommentNode(renderer, commentValue);
- // FIXME(misko): Attaching patch data is only needed for the root (Also add tests)
- attachPatchData(commentRNode, lView);
- }
- break;
- case ELEMENT_MARKER:
- const tagName = mutableOpCodes[++i];
- const elementNodeIndex = mutableOpCodes[++i];
- if (lView[elementNodeIndex] === null) {
- ngDevMode &&
- assertEqual(typeof tagName, 'string', `Expected "${tagName}" to be an element node tag name`);
- ngDevMode && ngDevMode.rendererCreateElement++;
- ngDevMode && assertIndexInExpandoRange(lView, elementNodeIndex);
- const elementRNode = lView[elementNodeIndex] =
- createElementNode(renderer, tagName, null);
- // FIXME(misko): Attaching patch data is only needed for the root (Also add tests)
- attachPatchData(elementRNode, lView);
- }
- break;
- default:
- ngDevMode &&
- throwError(`Unable to determine the type of mutate operation for "${opCode}"`);
- }
- }
- }
- }
- /**
- * Apply `I18nUpdateOpCodes` OpCodes
- *
- * @param tView Current `TView`
- * @param lView Current `LView`
- * @param updateOpCodes OpCodes to process
- * @param bindingsStartIndex Location of the first `ɵɵi18nApply`
- * @param changeMask Each bit corresponds to a `ɵɵi18nExp` (Counting backwards from
- * `bindingsStartIndex`)
- */
- function applyUpdateOpCodes(tView, lView, updateOpCodes, bindingsStartIndex, changeMask) {
- for (let i = 0; i < updateOpCodes.length; i++) {
- // bit code to check if we should apply the next update
- const checkBit = updateOpCodes[i];
- // Number of opCodes to skip until next set of update codes
- const skipCodes = updateOpCodes[++i];
- if (checkBit & changeMask) {
- // The value has been updated since last checked
- let value = '';
- for (let j = i + 1; j <= (i + skipCodes); j++) {
- const opCode = updateOpCodes[j];
- if (typeof opCode == 'string') {
- value += opCode;
- }
- else if (typeof opCode == 'number') {
- if (opCode < 0) {
- // Negative opCode represent `i18nExp` values offset.
- value += renderStringify(lView[bindingsStartIndex - opCode]);
- }
- else {
- const nodeIndex = (opCode >>> 2 /* I18nUpdateOpCode.SHIFT_REF */);
- switch (opCode & 3 /* I18nUpdateOpCode.MASK_OPCODE */) {
- case 1 /* I18nUpdateOpCode.Attr */:
- const propName = updateOpCodes[++j];
- const sanitizeFn = updateOpCodes[++j];
- const tNodeOrTagName = tView.data[nodeIndex];
- ngDevMode && assertDefined(tNodeOrTagName, 'Experting TNode or string');
- if (typeof tNodeOrTagName === 'string') {
- // IF we don't have a `TNode`, then we are an element in ICU (as ICU content does
- // not have TNode), in which case we know that there are no directives, and hence
- // we use attribute setting.
- setElementAttribute(lView[RENDERER], lView[nodeIndex], null, tNodeOrTagName, propName, value, sanitizeFn);
- }
- else {
- elementPropertyInternal(tView, tNodeOrTagName, lView, propName, value, lView[RENDERER], sanitizeFn, false);
- }
- break;
- case 0 /* I18nUpdateOpCode.Text */:
- const rText = lView[nodeIndex];
- rText !== null && updateTextNode(lView[RENDERER], rText, value);
- break;
- case 2 /* I18nUpdateOpCode.IcuSwitch */:
- applyIcuSwitchCase(tView, getTIcu(tView, nodeIndex), lView, value);
- break;
- case 3 /* I18nUpdateOpCode.IcuUpdate */:
- applyIcuUpdateCase(tView, getTIcu(tView, nodeIndex), bindingsStartIndex, lView);
- break;
- }
- }
- }
- }
- }
- else {
- const opCode = updateOpCodes[i + 1];
- if (opCode > 0 && (opCode & 3 /* I18nUpdateOpCode.MASK_OPCODE */) === 3 /* I18nUpdateOpCode.IcuUpdate */) {
- // Special case for the `icuUpdateCase`. It could be that the mask did not match, but
- // we still need to execute `icuUpdateCase` because the case has changed recently due to
- // previous `icuSwitchCase` instruction. (`icuSwitchCase` and `icuUpdateCase` always come in
- // pairs.)
- const nodeIndex = (opCode >>> 2 /* I18nUpdateOpCode.SHIFT_REF */);
- const tIcu = getTIcu(tView, nodeIndex);
- const currentIndex = lView[tIcu.currentCaseLViewIndex];
- if (currentIndex < 0) {
- applyIcuUpdateCase(tView, tIcu, bindingsStartIndex, lView);
- }
- }
- }
- i += skipCodes;
- }
- }
- /**
- * Apply OpCodes associated with updating an existing ICU.
- *
- * @param tView Current `TView`
- * @param tIcu Current `TIcu`
- * @param bindingsStartIndex Location of the first `ɵɵi18nApply`
- * @param lView Current `LView`
- */
- function applyIcuUpdateCase(tView, tIcu, bindingsStartIndex, lView) {
- ngDevMode && assertIndexInRange(lView, tIcu.currentCaseLViewIndex);
- let activeCaseIndex = lView[tIcu.currentCaseLViewIndex];
- if (activeCaseIndex !== null) {
- let mask = changeMask;
- if (activeCaseIndex < 0) {
- // Clear the flag.
- // Negative number means that the ICU was freshly created and we need to force the update.
- activeCaseIndex = lView[tIcu.currentCaseLViewIndex] = ~activeCaseIndex;
- // -1 is same as all bits on, which simulates creation since it marks all bits dirty
- mask = -1;
- }
- applyUpdateOpCodes(tView, lView, tIcu.update[activeCaseIndex], bindingsStartIndex, mask);
- }
- }
- /**
- * Apply OpCodes associated with switching a case on ICU.
- *
- * This involves tearing down existing case and than building up a new case.
- *
- * @param tView Current `TView`
- * @param tIcu Current `TIcu`
- * @param lView Current `LView`
- * @param value Value of the case to update to.
- */
- function applyIcuSwitchCase(tView, tIcu, lView, value) {
- // Rebuild a new case for this ICU
- const caseIndex = getCaseIndex(tIcu, value);
- let activeCaseIndex = getCurrentICUCaseIndex(tIcu, lView);
- if (activeCaseIndex !== caseIndex) {
- applyIcuSwitchCaseRemove(tView, tIcu, lView);
- lView[tIcu.currentCaseLViewIndex] = caseIndex === null ? null : ~caseIndex;
- if (caseIndex !== null) {
- // Add the nodes for the new case
- const anchorRNode = lView[tIcu.anchorIdx];
- if (anchorRNode) {
- ngDevMode && assertDomNode(anchorRNode);
- applyMutableOpCodes(tView, tIcu.create[caseIndex], lView, anchorRNode);
- }
- }
- }
- }
- /**
- * Apply OpCodes associated with tearing ICU case.
- *
- * This involves tearing down existing case and than building up a new case.
- *
- * @param tView Current `TView`
- * @param tIcu Current `TIcu`
- * @param lView Current `LView`
- */
- function applyIcuSwitchCaseRemove(tView, tIcu, lView) {
- let activeCaseIndex = getCurrentICUCaseIndex(tIcu, lView);
- if (activeCaseIndex !== null) {
- const removeCodes = tIcu.remove[activeCaseIndex];
- for (let i = 0; i < removeCodes.length; i++) {
- const nodeOrIcuIndex = removeCodes[i];
- if (nodeOrIcuIndex > 0) {
- // Positive numbers are `RNode`s.
- const rNode = getNativeByIndex(nodeOrIcuIndex, lView);
- rNode !== null && nativeRemoveNode(lView[RENDERER], rNode);
- }
- else {
- // Negative numbers are ICUs
- applyIcuSwitchCaseRemove(tView, getTIcu(tView, ~nodeOrIcuIndex), lView);
- }
- }
- }
- }
- /**
- * Returns the index of the current case of an ICU expression depending on the main binding value
- *
- * @param icuExpression
- * @param bindingValue The value of the main binding used by this ICU expression
- */
- function getCaseIndex(icuExpression, bindingValue) {
- let index = icuExpression.cases.indexOf(bindingValue);
- if (index === -1) {
- switch (icuExpression.type) {
- case 1 /* IcuType.plural */: {
- const resolvedCase = getPluralCase(bindingValue, getLocaleId());
- index = icuExpression.cases.indexOf(resolvedCase);
- if (index === -1 && resolvedCase !== 'other') {
- index = icuExpression.cases.indexOf('other');
- }
- break;
- }
- case 0 /* IcuType.select */: {
- index = icuExpression.cases.indexOf('other');
- break;
- }
- }
- }
- return index === -1 ? null : index;
- }
- function loadIcuContainerVisitor() {
- const _stack = [];
- let _index = -1;
- let _lView;
- let _removes;
- /**
- * Retrieves a set of root nodes from `TIcu.remove`. Used by `TNodeType.ICUContainer`
- * to determine which root belong to the ICU.
- *
- * Example of usage.
- * ```
- * const nextRNode = icuContainerIteratorStart(tIcuContainerNode, lView);
- * let rNode: RNode|null;
- * while(rNode = nextRNode()) {
- * console.log(rNode);
- * }
- * ```
- *
- * @param tIcuContainerNode Current `TIcuContainerNode`
- * @param lView `LView` where the `RNode`s should be looked up.
- */
- function icuContainerIteratorStart(tIcuContainerNode, lView) {
- _lView = lView;
- while (_stack.length)
- _stack.pop();
- ngDevMode && assertTNodeForLView(tIcuContainerNode, lView);
- enterIcu(tIcuContainerNode.value, lView);
- return icuContainerIteratorNext;
- }
- function enterIcu(tIcu, lView) {
- _index = 0;
- const currentCase = getCurrentICUCaseIndex(tIcu, lView);
- if (currentCase !== null) {
- ngDevMode && assertNumberInRange(currentCase, 0, tIcu.cases.length - 1);
- _removes = tIcu.remove[currentCase];
- }
- else {
- _removes = EMPTY_ARRAY;
- }
- }
- function icuContainerIteratorNext() {
- if (_index < _removes.length) {
- const removeOpCode = _removes[_index++];
- ngDevMode && assertNumber(removeOpCode, 'Expecting OpCode number');
- if (removeOpCode > 0) {
- const rNode = _lView[removeOpCode];
- ngDevMode && assertDomNode(rNode);
- return rNode;
- }
- else {
- _stack.push(_index, _removes);
- // ICUs are represented by negative indices
- const tIcuIndex = ~removeOpCode;
- const tIcu = _lView[TVIEW].data[tIcuIndex];
- ngDevMode && assertTIcu(tIcu);
- enterIcu(tIcu, _lView);
- return icuContainerIteratorNext();
- }
- }
- else {
- if (_stack.length === 0) {
- return null;
- }
- else {
- _removes = _stack.pop();
- _index = _stack.pop();
- return icuContainerIteratorNext();
- }
- }
- }
- return icuContainerIteratorStart;
- }
- /**
- * Converts `I18nCreateOpCodes` array into a human readable format.
- *
- * This function is attached to the `I18nCreateOpCodes.debug` property if `ngDevMode` is enabled.
- * This function provides a human readable view of the opcodes. This is useful when debugging the
- * application as well as writing more readable tests.
- *
- * @param this `I18nCreateOpCodes` if attached as a method.
- * @param opcodes `I18nCreateOpCodes` if invoked as a function.
- */
- function i18nCreateOpCodesToString(opcodes) {
- const createOpCodes = opcodes || (Array.isArray(this) ? this : []);
- let lines = [];
- for (let i = 0; i < createOpCodes.length; i++) {
- const opCode = createOpCodes[i++];
- const text = createOpCodes[i];
- const isComment = (opCode & I18nCreateOpCode.COMMENT) === I18nCreateOpCode.COMMENT;
- const appendNow = (opCode & I18nCreateOpCode.APPEND_EAGERLY) === I18nCreateOpCode.APPEND_EAGERLY;
- const index = opCode >>> I18nCreateOpCode.SHIFT;
- lines.push(`lView[${index}] = document.${isComment ? 'createComment' : 'createText'}(${JSON.stringify(text)});`);
- if (appendNow) {
- lines.push(`parent.appendChild(lView[${index}]);`);
- }
- }
- return lines;
- }
- /**
- * Converts `I18nUpdateOpCodes` array into a human readable format.
- *
- * This function is attached to the `I18nUpdateOpCodes.debug` property if `ngDevMode` is enabled.
- * This function provides a human readable view of the opcodes. This is useful when debugging the
- * application as well as writing more readable tests.
- *
- * @param this `I18nUpdateOpCodes` if attached as a method.
- * @param opcodes `I18nUpdateOpCodes` if invoked as a function.
- */
- function i18nUpdateOpCodesToString(opcodes) {
- const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
- let lines = [];
- function consumeOpCode(value) {
- const ref = value >>> 2 /* I18nUpdateOpCode.SHIFT_REF */;
- const opCode = value & 3 /* I18nUpdateOpCode.MASK_OPCODE */;
- switch (opCode) {
- case 0 /* I18nUpdateOpCode.Text */:
- return `(lView[${ref}] as Text).textContent = $$$`;
- case 1 /* I18nUpdateOpCode.Attr */:
- const attrName = parser.consumeString();
- const sanitizationFn = parser.consumeFunction();
- const value = sanitizationFn ? `(${sanitizationFn})($$$)` : '$$$';
- return `(lView[${ref}] as Element).setAttribute('${attrName}', ${value})`;
- case 2 /* I18nUpdateOpCode.IcuSwitch */:
- return `icuSwitchCase(${ref}, $$$)`;
- case 3 /* I18nUpdateOpCode.IcuUpdate */:
- return `icuUpdateCase(${ref})`;
- }
- throw new Error('unexpected OpCode');
- }
- while (parser.hasMore()) {
- let mask = parser.consumeNumber();
- let size = parser.consumeNumber();
- const end = parser.i + size;
- const statements = [];
- let statement = '';
- while (parser.i < end) {
- let value = parser.consumeNumberOrString();
- if (typeof value === 'string') {
- statement += value;
- }
- else if (value < 0) {
- // Negative numbers are ref indexes
- // Here `i` refers to current binding index. It is to signify that the value is relative,
- // rather than absolute.
- statement += '${lView[i' + value + ']}';
- }
- else {
- // Positive numbers are operations.
- const opCodeText = consumeOpCode(value);
- statements.push(opCodeText.replace('$$$', '`' + statement + '`') + ';');
- statement = '';
- }
- }
- lines.push(`if (mask & 0b${mask.toString(2)}) { ${statements.join(' ')} }`);
- }
- return lines;
- }
- /**
- * Converts `I18nCreateOpCodes` array into a human readable format.
- *
- * This function is attached to the `I18nCreateOpCodes.debug` if `ngDevMode` is enabled. This
- * function provides a human readable view of the opcodes. This is useful when debugging the
- * application as well as writing more readable tests.
- *
- * @param this `I18nCreateOpCodes` if attached as a method.
- * @param opcodes `I18nCreateOpCodes` if invoked as a function.
- */
- function icuCreateOpCodesToString(opcodes) {
- const parser = new OpCodeParser(opcodes || (Array.isArray(this) ? this : []));
- let lines = [];
- function consumeOpCode(opCode) {
- const parent = getParentFromIcuCreateOpCode(opCode);
- const ref = getRefFromIcuCreateOpCode(opCode);
- switch (getInstructionFromIcuCreateOpCode(opCode)) {
- case 0 /* IcuCreateOpCode.AppendChild */:
- return `(lView[${parent}] as Element).appendChild(lView[${lastRef}])`;
- case 1 /* IcuCreateOpCode.Attr */:
- return `(lView[${ref}] as Element).setAttribute("${parser.consumeString()}", "${parser.consumeString()}")`;
- }
- throw new Error('Unexpected OpCode: ' + getInstructionFromIcuCreateOpCode(opCode));
- }
- let lastRef = -1;
- while (parser.hasMore()) {
- let value = parser.consumeNumberStringOrMarker();
- if (value === ICU_MARKER) {
- const text = parser.consumeString();
- lastRef = parser.consumeNumber();
- lines.push(`lView[${lastRef}] = document.createComment("${text}")`);
- }
- else if (value === ELEMENT_MARKER) {
- const text = parser.consumeString();
- lastRef = parser.consumeNumber();
- lines.push(`lView[${lastRef}] = document.createElement("${text}")`);
- }
- else if (typeof value === 'string') {
- lastRef = parser.consumeNumber();
- lines.push(`lView[${lastRef}] = document.createTextNode("${value}")`);
- }
- else if (typeof value === 'number') {
- const line = consumeOpCode(value);
- line && lines.push(line);
- }
- else {
- throw new Error('Unexpected value');
- }
- }
- return lines;
- }
- /**
- * Converts `I18nRemoveOpCodes` array into a human readable format.
- *
- * This function is attached to the `I18nRemoveOpCodes.debug` if `ngDevMode` is enabled. This
- * function provides a human readable view of the opcodes. This is useful when debugging the
- * application as well as writing more readable tests.
- *
- * @param this `I18nRemoveOpCodes` if attached as a method.
- * @param opcodes `I18nRemoveOpCodes` if invoked as a function.
- */
- function i18nRemoveOpCodesToString(opcodes) {
- const removeCodes = opcodes || (Array.isArray(this) ? this : []);
- let lines = [];
- for (let i = 0; i < removeCodes.length; i++) {
- const nodeOrIcuIndex = removeCodes[i];
- if (nodeOrIcuIndex > 0) {
- // Positive numbers are `RNode`s.
- lines.push(`remove(lView[${nodeOrIcuIndex}])`);
- }
- else {
- // Negative numbers are ICUs
- lines.push(`removeNestedICU(${~nodeOrIcuIndex})`);
- }
- }
- return lines;
- }
- class OpCodeParser {
- constructor(codes) {
- this.i = 0;
- this.codes = codes;
- }
- hasMore() {
- return this.i < this.codes.length;
- }
- consumeNumber() {
- let value = this.codes[this.i++];
- assertNumber(value, 'expecting number in OpCode');
- return value;
- }
- consumeString() {
- let value = this.codes[this.i++];
- assertString(value, 'expecting string in OpCode');
- return value;
- }
- consumeFunction() {
- let value = this.codes[this.i++];
- if (value === null || typeof value === 'function') {
- return value;
- }
- throw new Error('expecting function in OpCode');
- }
- consumeNumberOrString() {
- let value = this.codes[this.i++];
- if (typeof value === 'string') {
- return value;
- }
- assertNumber(value, 'expecting number or string in OpCode');
- return value;
- }
- consumeNumberStringOrMarker() {
- let value = this.codes[this.i++];
- if (typeof value === 'string' || typeof value === 'number' || value == ICU_MARKER ||
- value == ELEMENT_MARKER) {
- return value;
- }
- assertNumber(value, 'expecting number, string, ICU_MARKER or ELEMENT_MARKER in OpCode');
- return value;
- }
- }
- const BINDING_REGEXP = /�(\d+):?\d*�/gi;
- const ICU_REGEXP = /({\s*�\d+:?\d*�\s*,\s*\S{6}\s*,[\s\S]*})/gi;
- const NESTED_ICU = /�(\d+)�/;
- const ICU_BLOCK_REGEXP = /^\s*(�\d+:?\d*�)\s*,\s*(select|plural)\s*,/;
- const MARKER = `�`;
- const SUBTEMPLATE_REGEXP = /�\/?\*(\d+:\d+)�/gi;
- const PH_REGEXP = /�(\/?[#*]\d+):?\d*�/gi;
- /**
- * Angular uses the special entity &ngsp; as a placeholder for non-removable space.
- * It's replaced by the 0xE500 PUA (Private Use Areas) unicode character and later on replaced by a
- * space.
- * We are re-implementing the same idea since translations might contain this special character.
- */
- const NGSP_UNICODE_REGEXP = /\uE500/g;
- function replaceNgsp(value) {
- return value.replace(NGSP_UNICODE_REGEXP, ' ');
- }
- /**
- * Patch a `debug` property getter on top of the existing object.
- *
- * NOTE: always call this method with `ngDevMode && attachDebugObject(...)`
- *
- * @param obj Object to patch
- * @param debugGetter Getter returning a value to patch
- */
- function attachDebugGetter(obj, debugGetter) {
- if (ngDevMode) {
- Object.defineProperty(obj, 'debug', { get: debugGetter, enumerable: false });
- }
- else {
- throw new Error('This method should be guarded with `ngDevMode` so that it can be tree shaken in production!');
- }
- }
- /**
- * Create dynamic nodes from i18n translation block.
- *
- * - Text nodes are created synchronously
- * - TNodes are linked into tree lazily
- *
- * @param tView Current `TView`
- * @parentTNodeIndex index to the parent TNode of this i18n block
- * @param lView Current `LView`
- * @param index Index of `ɵɵi18nStart` instruction.
- * @param message Message to translate.
- * @param subTemplateIndex Index into the sub template of message translation. (ie in case of
- * `ngIf`) (-1 otherwise)
- */
- function i18nStartFirstCreatePass(tView, parentTNodeIndex, lView, index, message, subTemplateIndex) {
- const rootTNode = getCurrentParentTNode();
- const createOpCodes = [];
- const updateOpCodes = [];
- const existingTNodeStack = [[]];
- if (ngDevMode) {
- attachDebugGetter(createOpCodes, i18nCreateOpCodesToString);
- attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
- }
- message = getTranslationForTemplate(message, subTemplateIndex);
- const msgParts = replaceNgsp(message).split(PH_REGEXP);
- for (let i = 0; i < msgParts.length; i++) {
- let value = msgParts[i];
- if ((i & 1) === 0) {
- // Even indexes are text (including bindings & ICU expressions)
- const parts = i18nParseTextIntoPartsAndICU(value);
- for (let j = 0; j < parts.length; j++) {
- let part = parts[j];
- if ((j & 1) === 0) {
- // `j` is odd therefore `part` is string
- const text = part;
- ngDevMode && assertString(text, 'Parsed ICU part should be string');
- if (text !== '') {
- i18nStartFirstCreatePassProcessTextNode(tView, rootTNode, existingTNodeStack[0], createOpCodes, updateOpCodes, lView, text);
- }
- }
- else {
- // `j` is Even therefor `part` is an `ICUExpression`
- const icuExpression = part;
- // Verify that ICU expression has the right shape. Translations might contain invalid
- // constructions (while original messages were correct), so ICU parsing at runtime may
- // not succeed (thus `icuExpression` remains a string).
- // Note: we intentionally retain the error here by not using `ngDevMode`, because
- // the value can change based on the locale and users aren't guaranteed to hit
- // an invalid string while they're developing.
- if (typeof icuExpression !== 'object') {
- throw new Error(`Unable to parse ICU expression in "${message}" message.`);
- }
- const icuContainerTNode = createTNodeAndAddOpCode(tView, rootTNode, existingTNodeStack[0], lView, createOpCodes, ngDevMode ? `ICU ${index}:${icuExpression.mainBinding}` : '', true);
- const icuNodeIndex = icuContainerTNode.index;
- ngDevMode &&
- assertGreaterThanOrEqual(icuNodeIndex, HEADER_OFFSET, 'Index must be in absolute LView offset');
- icuStart(tView, lView, updateOpCodes, parentTNodeIndex, icuExpression, icuNodeIndex);
- }
- }
- }
- else {
- // Odd indexes are placeholders (elements and sub-templates)
- // At this point value is something like: '/#1:2' (originally coming from '�/#1:2�')
- const isClosing = value.charCodeAt(0) === 47 /* CharCode.SLASH */;
- const type = value.charCodeAt(isClosing ? 1 : 0);
- ngDevMode && assertOneOf(type, 42 /* CharCode.STAR */, 35 /* CharCode.HASH */);
- const index = HEADER_OFFSET + Number.parseInt(value.substring((isClosing ? 2 : 1)));
- if (isClosing) {
- existingTNodeStack.shift();
- setCurrentTNode(getCurrentParentTNode(), false);
- }
- else {
- const tNode = createTNodePlaceholder(tView, existingTNodeStack[0], index);
- existingTNodeStack.unshift([]);
- setCurrentTNode(tNode, true);
- }
- }
- }
- tView.data[index] = {
- create: createOpCodes,
- update: updateOpCodes,
- };
- }
- /**
- * Allocate space in i18n Range add create OpCode instruction to create a text or comment node.
- *
- * @param tView Current `TView` needed to allocate space in i18n range.
- * @param rootTNode Root `TNode` of the i18n block. This node determines if the new TNode will be
- * added as part of the `i18nStart` instruction or as part of the `TNode.insertBeforeIndex`.
- * @param existingTNodes internal state for `addTNodeAndUpdateInsertBeforeIndex`.
- * @param lView Current `LView` needed to allocate space in i18n range.
- * @param createOpCodes Array storing `I18nCreateOpCodes` where new opCodes will be added.
- * @param text Text to be added when the `Text` or `Comment` node will be created.
- * @param isICU true if a `Comment` node for ICU (instead of `Text`) node should be created.
- */
- function createTNodeAndAddOpCode(tView, rootTNode, existingTNodes, lView, createOpCodes, text, isICU) {
- const i18nNodeIdx = allocExpando(tView, lView, 1, null);
- let opCode = i18nNodeIdx << I18nCreateOpCode.SHIFT;
- let parentTNode = getCurrentParentTNode();
- if (rootTNode === parentTNode) {
- // FIXME(misko): A null `parentTNode` should represent when we fall of the `LView` boundary.
- // (there is no parent), but in some circumstances (because we are inconsistent about how we set
- // `previousOrParentTNode`) it could point to `rootTNode` So this is a work around.
- parentTNode = null;
- }
- if (parentTNode === null) {
- // If we don't have a parent that means that we can eagerly add nodes.
- // If we have a parent than these nodes can't be added now (as the parent has not been created
- // yet) and instead the `parentTNode` is responsible for adding it. See
- // `TNode.insertBeforeIndex`
- opCode |= I18nCreateOpCode.APPEND_EAGERLY;
- }
- if (isICU) {
- opCode |= I18nCreateOpCode.COMMENT;
- ensureIcuContainerVisitorLoaded(loadIcuContainerVisitor);
- }
- createOpCodes.push(opCode, text === null ? '' : text);
- // We store `{{?}}` so that when looking at debug `TNodeType.template` we can see where the
- // bindings are.
- const tNode = createTNodeAtIndex(tView, i18nNodeIdx, isICU ? 32 /* TNodeType.Icu */ : 1 /* TNodeType.Text */, text === null ? (ngDevMode ? '{{?}}' : '') : text, null);
- addTNodeAndUpdateInsertBeforeIndex(existingTNodes, tNode);
- const tNodeIdx = tNode.index;
- setCurrentTNode(tNode, false /* Text nodes are self closing */);
- if (parentTNode !== null && rootTNode !== parentTNode) {
- // We are a child of deeper node (rather than a direct child of `i18nStart` instruction.)
- // We have to make sure to add ourselves to the parent.
- setTNodeInsertBeforeIndex(parentTNode, tNodeIdx);
- }
- return tNode;
- }
- /**
- * Processes text node in i18n block.
- *
- * Text nodes can have:
- * - Create instruction in `createOpCodes` for creating the text node.
- * - Allocate spec for text node in i18n range of `LView`
- * - If contains binding:
- * - bindings => allocate space in i18n range of `LView` to store the binding value.
- * - populate `updateOpCodes` with update instructions.
- *
- * @param tView Current `TView`
- * @param rootTNode Root `TNode` of the i18n block. This node determines if the new TNode will
- * be added as part of the `i18nStart` instruction or as part of the
- * `TNode.insertBeforeIndex`.
- * @param existingTNodes internal state for `addTNodeAndUpdateInsertBeforeIndex`.
- * @param createOpCodes Location where the creation OpCodes will be stored.
- * @param lView Current `LView`
- * @param text The translated text (which may contain binding)
- */
- function i18nStartFirstCreatePassProcessTextNode(tView, rootTNode, existingTNodes, createOpCodes, updateOpCodes, lView, text) {
- const hasBinding = text.match(BINDING_REGEXP);
- const tNode = createTNodeAndAddOpCode(tView, rootTNode, existingTNodes, lView, createOpCodes, hasBinding ? null : text, false);
- if (hasBinding) {
- generateBindingUpdateOpCodes(updateOpCodes, text, tNode.index, null, 0, null);
- }
- }
- /**
- * See `i18nAttributes` above.
- */
- function i18nAttributesFirstPass(tView, index, values) {
- const previousElement = getCurrentTNode();
- const previousElementIndex = previousElement.index;
- const updateOpCodes = [];
- if (ngDevMode) {
- attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
- }
- if (tView.firstCreatePass && tView.data[index] === null) {
- for (let i = 0; i < values.length; i += 2) {
- const attrName = values[i];
- const message = values[i + 1];
- if (message !== '') {
- // Check if attribute value contains an ICU and throw an error if that's the case.
- // ICUs in element attributes are not supported.
- // Note: we intentionally retain the error here by not using `ngDevMode`, because
- // the `value` can change based on the locale and users aren't guaranteed to hit
- // an invalid string while they're developing.
- if (ICU_REGEXP.test(message)) {
- throw new Error(`ICU expressions are not supported in attributes. Message: "${message}".`);
- }
- // i18n attributes that hit this code path are guaranteed to have bindings, because
- // the compiler treats static i18n attributes as regular attribute bindings.
- // Since this may not be the first i18n attribute on this element we need to pass in how
- // many previous bindings there have already been.
- generateBindingUpdateOpCodes(updateOpCodes, message, previousElementIndex, attrName, countBindings(updateOpCodes), null);
- }
- }
- tView.data[index] = updateOpCodes;
- }
- }
- /**
- * Generate the OpCodes to update the bindings of a string.
- *
- * @param updateOpCodes Place where the update opcodes will be stored.
- * @param str The string containing the bindings.
- * @param destinationNode Index of the destination node which will receive the binding.
- * @param attrName Name of the attribute, if the string belongs to an attribute.
- * @param sanitizeFn Sanitization function used to sanitize the string after update, if necessary.
- * @param bindingStart The lView index of the next expression that can be bound via an opCode.
- * @returns The mask value for these bindings
- */
- function generateBindingUpdateOpCodes(updateOpCodes, str, destinationNode, attrName, bindingStart, sanitizeFn) {
- ngDevMode &&
- assertGreaterThanOrEqual(destinationNode, HEADER_OFFSET, 'Index must be in absolute LView offset');
- const maskIndex = updateOpCodes.length; // Location of mask
- const sizeIndex = maskIndex + 1; // location of size for skipping
- updateOpCodes.push(null, null); // Alloc space for mask and size
- const startIndex = maskIndex + 2; // location of first allocation.
- if (ngDevMode) {
- attachDebugGetter(updateOpCodes, i18nUpdateOpCodesToString);
- }
- const textParts = str.split(BINDING_REGEXP);
- let mask = 0;
- for (let j = 0; j < textParts.length; j++) {
- const textValue = textParts[j];
- if (j & 1) {
- // Odd indexes are bindings
- const bindingIndex = bindingStart + parseInt(textValue, 10);
- updateOpCodes.push(-1 - bindingIndex);
- mask = mask | toMaskBit(bindingIndex);
- }
- else if (textValue !== '') {
- // Even indexes are text
- updateOpCodes.push(textValue);
- }
- }
- updateOpCodes.push(destinationNode << 2 /* I18nUpdateOpCode.SHIFT_REF */ |
- (attrName ? 1 /* I18nUpdateOpCode.Attr */ : 0 /* I18nUpdateOpCode.Text */));
- if (attrName) {
- updateOpCodes.push(attrName, sanitizeFn);
- }
- updateOpCodes[maskIndex] = mask;
- updateOpCodes[sizeIndex] = updateOpCodes.length - startIndex;
- return mask;
- }
- /**
- * Count the number of bindings in the given `opCodes`.
- *
- * It could be possible to speed this up, by passing the number of bindings found back from
- * `generateBindingUpdateOpCodes()` to `i18nAttributesFirstPass()` but this would then require more
- * complexity in the code and/or transient objects to be created.
- *
- * Since this function is only called once when the template is instantiated, is trivial in the
- * first instance (since `opCodes` will be an empty array), and it is not common for elements to
- * contain multiple i18n bound attributes, it seems like this is a reasonable compromise.
- */
- function countBindings(opCodes) {
- let count = 0;
- for (let i = 0; i < opCodes.length; i++) {
- const opCode = opCodes[i];
- // Bindings are negative numbers.
- if (typeof opCode === 'number' && opCode < 0) {
- count++;
- }
- }
- return count;
- }
- /**
- * Convert binding index to mask bit.
- *
- * Each index represents a single bit on the bit-mask. Because bit-mask only has 32 bits, we make
- * the 32nd bit share all masks for all bindings higher than 32. Since it is extremely rare to
- * have more than 32 bindings this will be hit very rarely. The downside of hitting this corner
- * case is that we will execute binding code more often than necessary. (penalty of performance)
- */
- function toMaskBit(bindingIndex) {
- return 1 << Math.min(bindingIndex, 31);
- }
- function isRootTemplateMessage(subTemplateIndex) {
- return subTemplateIndex === -1;
- }
- /**
- * Removes everything inside the sub-templates of a message.
- */
- function removeInnerTemplateTranslation(message) {
- let match;
- let res = '';
- let index = 0;
- let inTemplate = false;
- let tagMatched;
- while ((match = SUBTEMPLATE_REGEXP.exec(message)) !== null) {
- if (!inTemplate) {
- res += message.substring(index, match.index + match[0].length);
- tagMatched = match[1];
- inTemplate = true;
- }
- else {
- if (match[0] === `${MARKER}/*${tagMatched}${MARKER}`) {
- index = match.index;
- inTemplate = false;
- }
- }
- }
- ngDevMode &&
- assertEqual(inTemplate, false, `Tag mismatch: unable to find the end of the sub-template in the translation "${message}"`);
- res += message.slice(index);
- return res;
- }
- /**
- * Extracts a part of a message and removes the rest.
- *
- * This method is used for extracting a part of the message associated with a template. A
- * translated message can span multiple templates.
- *
- * Example:
- * ```
- * <div i18n>Translate <span *ngIf>me</span>!</div>
- * ```
- *
- * @param message The message to crop
- * @param subTemplateIndex Index of the sub-template to extract. If undefined it returns the
- * external template and removes all sub-templates.
- */
- function getTranslationForTemplate(message, subTemplateIndex) {
- if (isRootTemplateMessage(subTemplateIndex)) {
- // We want the root template message, ignore all sub-templates
- return removeInnerTemplateTranslation(message);
- }
- else {
- // We want a specific sub-template
- const start = message.indexOf(`:${subTemplateIndex}${MARKER}`) + 2 + subTemplateIndex.toString().length;
- const end = message.search(new RegExp(`${MARKER}\\/\\*\\d+:${subTemplateIndex}${MARKER}`));
- return removeInnerTemplateTranslation(message.substring(start, end));
- }
- }
- /**
- * Generate the OpCodes for ICU expressions.
- *
- * @param icuExpression
- * @param index Index where the anchor is stored and an optional `TIcuContainerNode`
- * - `lView[anchorIdx]` points to a `Comment` node representing the anchor for the ICU.
- * - `tView.data[anchorIdx]` points to the `TIcuContainerNode` if ICU is root (`null` otherwise)
- */
- function icuStart(tView, lView, updateOpCodes, parentIdx, icuExpression, anchorIdx) {
- ngDevMode && assertDefined(icuExpression, 'ICU expression must be defined');
- let bindingMask = 0;
- const tIcu = {
- type: icuExpression.type,
- currentCaseLViewIndex: allocExpando(tView, lView, 1, null),
- anchorIdx,
- cases: [],
- create: [],
- remove: [],
- update: []
- };
- addUpdateIcuSwitch(updateOpCodes, icuExpression, anchorIdx);
- setTIcu(tView, anchorIdx, tIcu);
- const values = icuExpression.values;
- for (let i = 0; i < values.length; i++) {
- // Each value is an array of strings & other ICU expressions
- const valueArr = values[i];
- const nestedIcus = [];
- for (let j = 0; j < valueArr.length; j++) {
- const value = valueArr[j];
- if (typeof value !== 'string') {
- // It is an nested ICU expression
- const icuIndex = nestedIcus.push(value) - 1;
- // Replace nested ICU expression by a comment node
- valueArr[j] = `<!--�${icuIndex}�-->`;
- }
- }
- bindingMask = parseIcuCase(tView, tIcu, lView, updateOpCodes, parentIdx, icuExpression.cases[i], valueArr.join(''), nestedIcus) |
- bindingMask;
- }
- if (bindingMask) {
- addUpdateIcuUpdate(updateOpCodes, bindingMask, anchorIdx);
- }
- }
- /**
- * Parses text containing an ICU expression and produces a JSON object for it.
- * Original code from closure library, modified for Angular.
- *
- * @param pattern Text containing an ICU expression that needs to be parsed.
- *
- */
- function parseICUBlock(pattern) {
- const cases = [];
- const values = [];
- let icuType = 1 /* IcuType.plural */;
- let mainBinding = 0;
- pattern = pattern.replace(ICU_BLOCK_REGEXP, function (str, binding, type) {
- if (type === 'select') {
- icuType = 0 /* IcuType.select */;
- }
- else {
- icuType = 1 /* IcuType.plural */;
- }
- mainBinding = parseInt(binding.slice(1), 10);
- return '';
- });
- const parts = i18nParseTextIntoPartsAndICU(pattern);
- // Looking for (key block)+ sequence. One of the keys has to be "other".
- for (let pos = 0; pos < parts.length;) {
- let key = parts[pos++].trim();
- if (icuType === 1 /* IcuType.plural */) {
- // Key can be "=x", we just want "x"
- key = key.replace(/\s*(?:=)?(\w+)\s*/, '$1');
- }
- if (key.length) {
- cases.push(key);
- }
- const blocks = i18nParseTextIntoPartsAndICU(parts[pos++]);
- if (cases.length > values.length) {
- values.push(blocks);
- }
- }
- // TODO(ocombe): support ICU expressions in attributes, see #21615
- return { type: icuType, mainBinding: mainBinding, cases, values };
- }
- /**
- * Breaks pattern into strings and top level {...} blocks.
- * Can be used to break a message into text and ICU expressions, or to break an ICU expression
- * into keys and cases. Original code from closure library, modified for Angular.
- *
- * @param pattern (sub)Pattern to be broken.
- * @returns An `Array<string|IcuExpression>` where:
- * - odd positions: `string` => text between ICU expressions
- * - even positions: `ICUExpression` => ICU expression parsed into `ICUExpression` record.
- */
- function i18nParseTextIntoPartsAndICU(pattern) {
- if (!pattern) {
- return [];
- }
- let prevPos = 0;
- const braceStack = [];
- const results = [];
- const braces = /[{}]/g;
- // lastIndex doesn't get set to 0 so we have to.
- braces.lastIndex = 0;
- let match;
- while (match = braces.exec(pattern)) {
- const pos = match.index;
- if (match[0] == '}') {
- braceStack.pop();
- if (braceStack.length == 0) {
- // End of the block.
- const block = pattern.substring(prevPos, pos);
- if (ICU_BLOCK_REGEXP.test(block)) {
- results.push(parseICUBlock(block));
- }
- else {
- results.push(block);
- }
- prevPos = pos + 1;
- }
- }
- else {
- if (braceStack.length == 0) {
- const substring = pattern.substring(prevPos, pos);
- results.push(substring);
- prevPos = pos + 1;
- }
- braceStack.push('{');
- }
- }
- const substring = pattern.substring(prevPos);
- results.push(substring);
- return results;
- }
- /**
- * Parses a node, its children and its siblings, and generates the mutate & update OpCodes.
- *
- */
- function parseIcuCase(tView, tIcu, lView, updateOpCodes, parentIdx, caseName, unsafeCaseHtml, nestedIcus) {
- const create = [];
- const remove = [];
- const update = [];
- if (ngDevMode) {
- attachDebugGetter(create, icuCreateOpCodesToString);
- attachDebugGetter(remove, i18nRemoveOpCodesToString);
- attachDebugGetter(update, i18nUpdateOpCodesToString);
- }
- tIcu.cases.push(caseName);
- tIcu.create.push(create);
- tIcu.remove.push(remove);
- tIcu.update.push(update);
- const inertBodyHelper = getInertBodyHelper(getDocument());
- const inertBodyElement = inertBodyHelper.getInertBodyElement(unsafeCaseHtml);
- ngDevMode && assertDefined(inertBodyElement, 'Unable to generate inert body element');
- const inertRootNode = getTemplateContent(inertBodyElement) || inertBodyElement;
- if (inertRootNode) {
- return walkIcuTree(tView, tIcu, lView, updateOpCodes, create, remove, update, inertRootNode, parentIdx, nestedIcus, 0);
- }
- else {
- return 0;
- }
- }
- function walkIcuTree(tView, tIcu, lView, sharedUpdateOpCodes, create, remove, update, parentNode, parentIdx, nestedIcus, depth) {
- let bindingMask = 0;
- let currentNode = parentNode.firstChild;
- while (currentNode) {
- const newIndex = allocExpando(tView, lView, 1, null);
- switch (currentNode.nodeType) {
- case Node.ELEMENT_NODE:
- const element = currentNode;
- const tagName = element.tagName.toLowerCase();
- if (VALID_ELEMENTS.hasOwnProperty(tagName)) {
- addCreateNodeAndAppend(create, ELEMENT_MARKER, tagName, parentIdx, newIndex);
- tView.data[newIndex] = tagName;
- const elAttrs = element.attributes;
- for (let i = 0; i < elAttrs.length; i++) {
- const attr = elAttrs.item(i);
- const lowerAttrName = attr.name.toLowerCase();
- const hasBinding = !!attr.value.match(BINDING_REGEXP);
- // we assume the input string is safe, unless it's using a binding
- if (hasBinding) {
- if (VALID_ATTRS.hasOwnProperty(lowerAttrName)) {
- if (URI_ATTRS[lowerAttrName]) {
- generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, _sanitizeUrl);
- }
- else {
- generateBindingUpdateOpCodes(update, attr.value, newIndex, attr.name, 0, null);
- }
- }
- else {
- ngDevMode &&
- console.warn(`WARNING: ignoring unsafe attribute value ` +
- `${lowerAttrName} on element ${tagName} ` +
- `(see ${XSS_SECURITY_URL})`);
- }
- }
- else {
- addCreateAttribute(create, newIndex, attr);
- }
- }
- // Parse the children of this node (if any)
- bindingMask = walkIcuTree(tView, tIcu, lView, sharedUpdateOpCodes, create, remove, update, currentNode, newIndex, nestedIcus, depth + 1) |
- bindingMask;
- addRemoveNode(remove, newIndex, depth);
- }
- break;
- case Node.TEXT_NODE:
- const value = currentNode.textContent || '';
- const hasBinding = value.match(BINDING_REGEXP);
- addCreateNodeAndAppend(create, null, hasBinding ? '' : value, parentIdx, newIndex);
- addRemoveNode(remove, newIndex, depth);
- if (hasBinding) {
- bindingMask =
- generateBindingUpdateOpCodes(update, value, newIndex, null, 0, null) | bindingMask;
- }
- break;
- case Node.COMMENT_NODE:
- // Check if the comment node is a placeholder for a nested ICU
- const isNestedIcu = NESTED_ICU.exec(currentNode.textContent || '');
- if (isNestedIcu) {
- const nestedIcuIndex = parseInt(isNestedIcu[1], 10);
- const icuExpression = nestedIcus[nestedIcuIndex];
- // Create the comment node that will anchor the ICU expression
- addCreateNodeAndAppend(create, ICU_MARKER, ngDevMode ? `nested ICU ${nestedIcuIndex}` : '', parentIdx, newIndex);
- icuStart(tView, lView, sharedUpdateOpCodes, parentIdx, icuExpression, newIndex);
- addRemoveNestedIcu(remove, newIndex, depth);
- }
- break;
- }
- currentNode = currentNode.nextSibling;
- }
- return bindingMask;
- }
- function addRemoveNode(remove, index, depth) {
- if (depth === 0) {
- remove.push(index);
- }
- }
- function addRemoveNestedIcu(remove, index, depth) {
- if (depth === 0) {
- remove.push(~index); // remove ICU at `index`
- remove.push(index); // remove ICU comment at `index`
- }
- }
- function addUpdateIcuSwitch(update, icuExpression, index) {
- update.push(toMaskBit(icuExpression.mainBinding), 2, -1 - icuExpression.mainBinding, index << 2 /* I18nUpdateOpCode.SHIFT_REF */ | 2 /* I18nUpdateOpCode.IcuSwitch */);
- }
- function addUpdateIcuUpdate(update, bindingMask, index) {
- update.push(bindingMask, 1, index << 2 /* I18nUpdateOpCode.SHIFT_REF */ | 3 /* I18nUpdateOpCode.IcuUpdate */);
- }
- function addCreateNodeAndAppend(create, marker, text, appendToParentIdx, createAtIdx) {
- if (marker !== null) {
- create.push(marker);
- }
- create.push(text, createAtIdx, icuCreateOpCode(0 /* IcuCreateOpCode.AppendChild */, appendToParentIdx, createAtIdx));
- }
- function addCreateAttribute(create, newIndex, attr) {
- create.push(newIndex << 1 /* IcuCreateOpCode.SHIFT_REF */ | 1 /* IcuCreateOpCode.Attr */, attr.name, attr.value);
- }
- // i18nPostprocess consts
- const ROOT_TEMPLATE_ID = 0;
- const PP_MULTI_VALUE_PLACEHOLDERS_REGEXP = /\[(�.+?�?)\]/;
- const PP_PLACEHOLDERS_REGEXP = /\[(�.+?�?)\]|(�\/?\*\d+:\d+�)/g;
- const PP_ICU_VARS_REGEXP = /({\s*)(VAR_(PLURAL|SELECT)(_\d+)?)(\s*,)/g;
- const PP_ICU_PLACEHOLDERS_REGEXP = /{([A-Z0-9_]+)}/g;
- const PP_ICUS_REGEXP = /�I18N_EXP_(ICU(_\d+)?)�/g;
- const PP_CLOSE_TEMPLATE_REGEXP = /\/\*/;
- const PP_TEMPLATE_ID_REGEXP = /\d+\:(\d+)/;
- /**
- * Handles message string post-processing for internationalization.
- *
- * Handles message string post-processing by transforming it from intermediate
- * format (that might contain some markers that we need to replace) to the final
- * form, consumable by i18nStart instruction. Post processing steps include:
- *
- * 1. Resolve all multi-value cases (like [�*1:1��#2:1�|�#4:1�|�5�])
- * 2. Replace all ICU vars (like "VAR_PLURAL")
- * 3. Replace all placeholders used inside ICUs in a form of {PLACEHOLDER}
- * 4. Replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�)
- * in case multiple ICUs have the same placeholder name
- *
- * @param message Raw translation string for post processing
- * @param replacements Set of replacements that should be applied
- *
- * @returns Transformed string that can be consumed by i18nStart instruction
- *
- * @codeGenApi
- */
- function i18nPostprocess(message, replacements = {}) {
- /**
- * Step 1: resolve all multi-value placeholders like [�#5�|�*1:1��#2:1�|�#4:1�]
- *
- * Note: due to the way we process nested templates (BFS), multi-value placeholders are typically
- * grouped by templates, for example: [�#5�|�#6�|�#1:1�|�#3:2�] where �#5� and �#6� belong to root
- * template, �#1:1� belong to nested template with index 1 and �#1:2� - nested template with index
- * 3. However in real templates the order might be different: i.e. �#1:1� and/or �#3:2� may go in
- * front of �#6�. The post processing step restores the right order by keeping track of the
- * template id stack and looks for placeholders that belong to the currently active template.
- */
- let result = message;
- if (PP_MULTI_VALUE_PLACEHOLDERS_REGEXP.test(message)) {
- const matches = {};
- const templateIdsStack = [ROOT_TEMPLATE_ID];
- result = result.replace(PP_PLACEHOLDERS_REGEXP, (m, phs, tmpl) => {
- const content = phs || tmpl;
- const placeholders = matches[content] || [];
- if (!placeholders.length) {
- content.split('|').forEach((placeholder) => {
- const match = placeholder.match(PP_TEMPLATE_ID_REGEXP);
- const templateId = match ? parseInt(match[1], 10) : ROOT_TEMPLATE_ID;
- const isCloseTemplateTag = PP_CLOSE_TEMPLATE_REGEXP.test(placeholder);
- placeholders.push([templateId, isCloseTemplateTag, placeholder]);
- });
- matches[content] = placeholders;
- }
- if (!placeholders.length) {
- throw new Error(`i18n postprocess: unmatched placeholder - ${content}`);
- }
- const currentTemplateId = templateIdsStack[templateIdsStack.length - 1];
- let idx = 0;
- // find placeholder index that matches current template id
- for (let i = 0; i < placeholders.length; i++) {
- if (placeholders[i][0] === currentTemplateId) {
- idx = i;
- break;
- }
- }
- // update template id stack based on the current tag extracted
- const [templateId, isCloseTemplateTag, placeholder] = placeholders[idx];
- if (isCloseTemplateTag) {
- templateIdsStack.pop();
- }
- else if (currentTemplateId !== templateId) {
- templateIdsStack.push(templateId);
- }
- // remove processed tag from the list
- placeholders.splice(idx, 1);
- return placeholder;
- });
- }
- // return current result if no replacements specified
- if (!Object.keys(replacements).length) {
- return result;
- }
- /**
- * Step 2: replace all ICU vars (like "VAR_PLURAL")
- */
- result = result.replace(PP_ICU_VARS_REGEXP, (match, start, key, _type, _idx, end) => {
- return replacements.hasOwnProperty(key) ? `${start}${replacements[key]}${end}` : match;
- });
- /**
- * Step 3: replace all placeholders used inside ICUs in a form of {PLACEHOLDER}
- */
- result = result.replace(PP_ICU_PLACEHOLDERS_REGEXP, (match, key) => {
- return replacements.hasOwnProperty(key) ? replacements[key] : match;
- });
- /**
- * Step 4: replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�) in case
- * multiple ICUs have the same placeholder name
- */
- result = result.replace(PP_ICUS_REGEXP, (match, key) => {
- if (replacements.hasOwnProperty(key)) {
- const list = replacements[key];
- if (!list.length) {
- throw new Error(`i18n postprocess: unmatched ICU - ${match} with key: ${key}`);
- }
- return list.shift();
- }
- return match;
- });
- return result;
- }
- /**
- * Marks a block of text as translatable.
- *
- * The instructions `i18nStart` and `i18nEnd` mark the translation block in the template.
- * The translation `message` is the value which is locale specific. The translation string may
- * contain placeholders which associate inner elements and sub-templates within the translation.
- *
- * The translation `message` placeholders are:
- * - `�{index}(:{block})�`: *Binding Placeholder*: Marks a location where an expression will be
- * interpolated into. The placeholder `index` points to the expression binding index. An optional
- * `block` that matches the sub-template in which it was declared.
- * - `�#{index}(:{block})�`/`�/#{index}(:{block})�`: *Element Placeholder*: Marks the beginning
- * and end of DOM element that were embedded in the original translation block. The placeholder
- * `index` points to the element index in the template instructions set. An optional `block` that
- * matches the sub-template in which it was declared.
- * - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be
- * split up and translated separately in each angular template function. The `index` points to the
- * `template` instruction index. A `block` that matches the sub-template in which it was declared.
- *
- * @param index A unique index of the translation in the static block.
- * @param messageIndex An index of the translation message from the `def.consts` array.
- * @param subTemplateIndex Optional sub-template index in the `message`.
- *
- * @codeGenApi
- */
- function ɵɵi18nStart(index, messageIndex, subTemplateIndex = -1) {
- const tView = getTView();
- const lView = getLView();
- const adjustedIndex = HEADER_OFFSET + index;
- ngDevMode && assertDefined(tView, `tView should be defined`);
- const message = getConstant(tView.consts, messageIndex);
- const parentTNode = getCurrentParentTNode();
- if (tView.firstCreatePass) {
- i18nStartFirstCreatePass(tView, parentTNode === null ? 0 : parentTNode.index, lView, adjustedIndex, message, subTemplateIndex);
- }
- // Set a flag that this LView has i18n blocks.
- // The flag is later used to determine whether this component should
- // be hydrated (currently hydration is not supported for i18n blocks).
- if (tView.type === 2 /* TViewType.Embedded */) {
- // Annotate host component's LView (not embedded view's LView),
- // since hydration can be skipped on per-component basis only.
- const componentLView = lView[DECLARATION_COMPONENT_VIEW];
- componentLView[FLAGS] |= 32 /* LViewFlags.HasI18n */;
- }
- else {
- lView[FLAGS] |= 32 /* LViewFlags.HasI18n */;
- }
- const tI18n = tView.data[adjustedIndex];
- const sameViewParentTNode = parentTNode === lView[T_HOST] ? null : parentTNode;
- const parentRNode = getClosestRElement(tView, sameViewParentTNode, lView);
- // If `parentTNode` is an `ElementContainer` than it has `<!--ng-container--->`.
- // When we do inserts we have to make sure to insert in front of `<!--ng-container--->`.
- const insertInFrontOf = parentTNode && (parentTNode.type & 8 /* TNodeType.ElementContainer */) ?
- lView[parentTNode.index] :
- null;
- applyCreateOpCodes(lView, tI18n.create, parentRNode, insertInFrontOf);
- setInI18nBlock(true);
- }
- /**
- * Translates a translation block marked by `i18nStart` and `i18nEnd`. It inserts the text/ICU nodes
- * into the render tree, moves the placeholder nodes and removes the deleted nodes.
- *
- * @codeGenApi
- */
- function ɵɵi18nEnd() {
- setInI18nBlock(false);
- }
- /**
- *
- * Use this instruction to create a translation block that doesn't contain any placeholder.
- * It calls both {@link i18nStart} and {@link i18nEnd} in one instruction.
- *
- * The translation `message` is the value which is locale specific. The translation string may
- * contain placeholders which associate inner elements and sub-templates within the translation.
- *
- * The translation `message` placeholders are:
- * - `�{index}(:{block})�`: *Binding Placeholder*: Marks a location where an expression will be
- * interpolated into. The placeholder `index` points to the expression binding index. An optional
- * `block` that matches the sub-template in which it was declared.
- * - `�#{index}(:{block})�`/`�/#{index}(:{block})�`: *Element Placeholder*: Marks the beginning
- * and end of DOM element that were embedded in the original translation block. The placeholder
- * `index` points to the element index in the template instructions set. An optional `block` that
- * matches the sub-template in which it was declared.
- * - `�*{index}:{block}�`/`�/*{index}:{block}�`: *Sub-template Placeholder*: Sub-templates must be
- * split up and translated separately in each angular template function. The `index` points to the
- * `template` instruction index. A `block` that matches the sub-template in which it was declared.
- *
- * @param index A unique index of the translation in the static block.
- * @param messageIndex An index of the translation message from the `def.consts` array.
- * @param subTemplateIndex Optional sub-template index in the `message`.
- *
- * @codeGenApi
- */
- function ɵɵi18n(index, messageIndex, subTemplateIndex) {
- ɵɵi18nStart(index, messageIndex, subTemplateIndex);
- ɵɵi18nEnd();
- }
- /**
- * Marks a list of attributes as translatable.
- *
- * @param index A unique index in the static block
- * @param values
- *
- * @codeGenApi
- */
- function ɵɵi18nAttributes(index, attrsIndex) {
- const tView = getTView();
- ngDevMode && assertDefined(tView, `tView should be defined`);
- const attrs = getConstant(tView.consts, attrsIndex);
- i18nAttributesFirstPass(tView, index + HEADER_OFFSET, attrs);
- }
- /**
- * Stores the values of the bindings during each update cycle in order to determine if we need to
- * update the translated nodes.
- *
- * @param value The binding's value
- * @returns This function returns itself so that it may be chained
- * (e.g. `i18nExp(ctx.name)(ctx.title)`)
- *
- * @codeGenApi
- */
- function ɵɵi18nExp(value) {
- const lView = getLView();
- setMaskBit(bindingUpdated(lView, nextBindingIndex(), value));
- return ɵɵi18nExp;
- }
- /**
- * Updates a translation block or an i18n attribute when the bindings have changed.
- *
- * @param index Index of either {@link i18nStart} (translation block) or {@link i18nAttributes}
- * (i18n attribute) on which it should update the content.
- *
- * @codeGenApi
- */
- function ɵɵi18nApply(index) {
- applyI18n(getTView(), getLView(), index + HEADER_OFFSET);
- }
- /**
- * Handles message string post-processing for internationalization.
- *
- * Handles message string post-processing by transforming it from intermediate
- * format (that might contain some markers that we need to replace) to the final
- * form, consumable by i18nStart instruction. Post processing steps include:
- *
- * 1. Resolve all multi-value cases (like [�*1:1��#2:1�|�#4:1�|�5�])
- * 2. Replace all ICU vars (like "VAR_PLURAL")
- * 3. Replace all placeholders used inside ICUs in a form of {PLACEHOLDER}
- * 4. Replace all ICU references with corresponding values (like �ICU_EXP_ICU_1�)
- * in case multiple ICUs have the same placeholder name
- *
- * @param message Raw translation string for post processing
- * @param replacements Set of replacements that should be applied
- *
- * @returns Transformed string that can be consumed by i18nStart instruction
- *
- * @codeGenApi
- */
- function ɵɵi18nPostprocess(message, replacements = {}) {
- return i18nPostprocess(message, replacements);
- }
- /**
- * Creates runtime data structures for `{#defer}` blocks.
- *
- * @param index The index of the defer block in the data array
- * @param deferredDepsFn Function that contains dependencies for this defer block
- *
- * @codeGenApi
- */
- function ɵɵdefer(index, deferredDepsFn) {
- // TODO: implement runtime logic.
- }
- /*
- * This file re-exports all symbols contained in this directory.
- *
- * Why is this file not `index.ts`?
- *
- * There seems to be an inconsistent path resolution of an `index.ts` file
- * when only the parent directory is referenced. This could be due to the
- * node module resolution configuration differing from rollup and/or typescript.
- *
- * With commit
- * https://github.com/angular/angular/commit/d5e3f2c64bd13ce83e7c70788b7fc514ca4a9918
- * the `instructions.ts` file was moved to `instructions/instructions.ts` and an
- * `index.ts` file was used to re-export everything. Having had file names that were
- * importing from `instructions' directly (not the from the sub file or the `index.ts`
- * file) caused strange CI issues. `index.ts` had to be renamed to `all.ts` for this
- * to work.
- *
- * Jira Issue = FW-1184
- */
- /**
- * Resolves the providers which are defined in the DirectiveDef.
- *
- * When inserting the tokens and the factories in their respective arrays, we can assume that
- * this method is called first for the component (if any), and then for other directives on the same
- * node.
- * As a consequence,the providers are always processed in that order:
- * 1) The view providers of the component
- * 2) The providers of the component
- * 3) The providers of the other directives
- * This matches the structure of the injectables arrays of a view (for each node).
- * So the tokens and the factories can be pushed at the end of the arrays, except
- * in one case for multi providers.
- *
- * @param def the directive definition
- * @param providers: Array of `providers`.
- * @param viewProviders: Array of `viewProviders`.
- */
- function providersResolver(def, providers, viewProviders) {
- const tView = getTView();
- if (tView.firstCreatePass) {
- const isComponent = isComponentDef(def);
- // The list of view providers is processed first, and the flags are updated
- resolveProvider(viewProviders, tView.data, tView.blueprint, isComponent, true);
- // Then, the list of providers is processed, and the flags are updated
- resolveProvider(providers, tView.data, tView.blueprint, isComponent, false);
- }
- }
- /**
- * Resolves a provider and publishes it to the DI system.
- */
- function resolveProvider(provider, tInjectables, lInjectablesBlueprint, isComponent, isViewProvider) {
- provider = resolveForwardRef(provider);
- if (Array.isArray(provider)) {
- // Recursively call `resolveProvider`
- // Recursion is OK in this case because this code will not be in hot-path once we implement
- // cloning of the initial state.
- for (let i = 0; i < provider.length; i++) {
- resolveProvider(provider[i], tInjectables, lInjectablesBlueprint, isComponent, isViewProvider);
- }
- }
- else {
- const tView = getTView();
- const lView = getLView();
- const tNode = getCurrentTNode();
- let token = isTypeProvider(provider) ? provider : resolveForwardRef(provider.provide);
- const providerFactory = providerToFactory(provider);
- if (ngDevMode) {
- const injector = new NodeInjector(tNode, lView);
- runInInjectorProfilerContext(injector, token, () => {
- emitProviderConfiguredEvent(provider, isViewProvider);
- });
- }
- const beginIndex = tNode.providerIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */;
- const endIndex = tNode.directiveStart;
- const cptViewProvidersCount = tNode.providerIndexes >> 20 /* TNodeProviderIndexes.CptViewProvidersCountShift */;
- if (isTypeProvider(provider) || !provider.multi) {
- // Single provider case: the factory is created and pushed immediately
- const factory = new NodeInjectorFactory(providerFactory, isViewProvider, ɵɵdirectiveInject);
- const existingFactoryIndex = indexOf(token, tInjectables, isViewProvider ? beginIndex : beginIndex + cptViewProvidersCount, endIndex);
- if (existingFactoryIndex === -1) {
- diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, lView), tView, token);
- registerDestroyHooksIfSupported(tView, provider, tInjectables.length);
- tInjectables.push(token);
- tNode.directiveStart++;
- tNode.directiveEnd++;
- if (isViewProvider) {
- tNode.providerIndexes += 1048576 /* TNodeProviderIndexes.CptViewProvidersCountShifter */;
- }
- lInjectablesBlueprint.push(factory);
- lView.push(factory);
- }
- else {
- lInjectablesBlueprint[existingFactoryIndex] = factory;
- lView[existingFactoryIndex] = factory;
- }
- }
- else {
- // Multi provider case:
- // We create a multi factory which is going to aggregate all the values.
- // Since the output of such a factory depends on content or view injection,
- // we create two of them, which are linked together.
- //
- // The first one (for view providers) is always in the first block of the injectables array,
- // and the second one (for providers) is always in the second block.
- // This is important because view providers have higher priority. When a multi token
- // is being looked up, the view providers should be found first.
- // Note that it is not possible to have a multi factory in the third block (directive block).
- //
- // The algorithm to process multi providers is as follows:
- // 1) If the multi provider comes from the `viewProviders` of the component:
- // a) If the special view providers factory doesn't exist, it is created and pushed.
- // b) Else, the multi provider is added to the existing multi factory.
- // 2) If the multi provider comes from the `providers` of the component or of another
- // directive:
- // a) If the multi factory doesn't exist, it is created and provider pushed into it.
- // It is also linked to the multi factory for view providers, if it exists.
- // b) Else, the multi provider is added to the existing multi factory.
- const existingProvidersFactoryIndex = indexOf(token, tInjectables, beginIndex + cptViewProvidersCount, endIndex);
- const existingViewProvidersFactoryIndex = indexOf(token, tInjectables, beginIndex, beginIndex + cptViewProvidersCount);
- const doesProvidersFactoryExist = existingProvidersFactoryIndex >= 0 &&
- lInjectablesBlueprint[existingProvidersFactoryIndex];
- const doesViewProvidersFactoryExist = existingViewProvidersFactoryIndex >= 0 &&
- lInjectablesBlueprint[existingViewProvidersFactoryIndex];
- if (isViewProvider && !doesViewProvidersFactoryExist ||
- !isViewProvider && !doesProvidersFactoryExist) {
- // Cases 1.a and 2.a
- diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, lView), tView, token);
- const factory = multiFactory(isViewProvider ? multiViewProvidersFactoryResolver : multiProvidersFactoryResolver, lInjectablesBlueprint.length, isViewProvider, isComponent, providerFactory);
- if (!isViewProvider && doesViewProvidersFactoryExist) {
- lInjectablesBlueprint[existingViewProvidersFactoryIndex].providerFactory = factory;
- }
- registerDestroyHooksIfSupported(tView, provider, tInjectables.length, 0);
- tInjectables.push(token);
- tNode.directiveStart++;
- tNode.directiveEnd++;
- if (isViewProvider) {
- tNode.providerIndexes += 1048576 /* TNodeProviderIndexes.CptViewProvidersCountShifter */;
- }
- lInjectablesBlueprint.push(factory);
- lView.push(factory);
- }
- else {
- // Cases 1.b and 2.b
- const indexInFactory = multiFactoryAdd(lInjectablesBlueprint[isViewProvider ? existingViewProvidersFactoryIndex :
- existingProvidersFactoryIndex], providerFactory, !isViewProvider && isComponent);
- registerDestroyHooksIfSupported(tView, provider, existingProvidersFactoryIndex > -1 ? existingProvidersFactoryIndex :
- existingViewProvidersFactoryIndex, indexInFactory);
- }
- if (!isViewProvider && isComponent && doesViewProvidersFactoryExist) {
- lInjectablesBlueprint[existingViewProvidersFactoryIndex].componentProviders++;
- }
- }
- }
- }
- /**
- * Registers the `ngOnDestroy` hook of a provider, if the provider supports destroy hooks.
- * @param tView `TView` in which to register the hook.
- * @param provider Provider whose hook should be registered.
- * @param contextIndex Index under which to find the context for the hook when it's being invoked.
- * @param indexInFactory Only required for `multi` providers. Index of the provider in the multi
- * provider factory.
- */
- function registerDestroyHooksIfSupported(tView, provider, contextIndex, indexInFactory) {
- const providerIsTypeProvider = isTypeProvider(provider);
- const providerIsClassProvider = isClassProvider(provider);
- if (providerIsTypeProvider || providerIsClassProvider) {
- // Resolve forward references as `useClass` can hold a forward reference.
- const classToken = providerIsClassProvider ? resolveForwardRef(provider.useClass) : provider;
- const prototype = classToken.prototype;
- const ngOnDestroy = prototype.ngOnDestroy;
- if (ngOnDestroy) {
- const hooks = tView.destroyHooks || (tView.destroyHooks = []);
- if (!providerIsTypeProvider && provider.multi) {
- ngDevMode &&
- assertDefined(indexInFactory, 'indexInFactory when registering multi factory destroy hook');
- const existingCallbacksIndex = hooks.indexOf(contextIndex);
- if (existingCallbacksIndex === -1) {
- hooks.push(contextIndex, [indexInFactory, ngOnDestroy]);
- }
- else {
- hooks[existingCallbacksIndex + 1].push(indexInFactory, ngOnDestroy);
- }
- }
- else {
- hooks.push(contextIndex, ngOnDestroy);
- }
- }
- }
- }
- /**
- * Add a factory in a multi factory.
- * @returns Index at which the factory was inserted.
- */
- function multiFactoryAdd(multiFactory, factory, isComponentProvider) {
- if (isComponentProvider) {
- multiFactory.componentProviders++;
- }
- return multiFactory.multi.push(factory) - 1;
- }
- /**
- * Returns the index of item in the array, but only in the begin to end range.
- */
- function indexOf(item, arr, begin, end) {
- for (let i = begin; i < end; i++) {
- if (arr[i] === item)
- return i;
- }
- return -1;
- }
- /**
- * Use this with `multi` `providers`.
- */
- function multiProvidersFactoryResolver(_, tData, lData, tNode) {
- return multiResolve(this.multi, []);
- }
- /**
- * Use this with `multi` `viewProviders`.
- *
- * This factory knows how to concatenate itself with the existing `multi` `providers`.
- */
- function multiViewProvidersFactoryResolver(_, tData, lView, tNode) {
- const factories = this.multi;
- let result;
- if (this.providerFactory) {
- const componentCount = this.providerFactory.componentProviders;
- const multiProviders = getNodeInjectable(lView, lView[TVIEW], this.providerFactory.index, tNode);
- // Copy the section of the array which contains `multi` `providers` from the component
- result = multiProviders.slice(0, componentCount);
- // Insert the `viewProvider` instances.
- multiResolve(factories, result);
- // Copy the section of the array which contains `multi` `providers` from other directives
- for (let i = componentCount; i < multiProviders.length; i++) {
- result.push(multiProviders[i]);
- }
- }
- else {
- result = [];
- // Insert the `viewProvider` instances.
- multiResolve(factories, result);
- }
- return result;
- }
- /**
- * Maps an array of factories into an array of values.
- */
- function multiResolve(factories, result) {
- for (let i = 0; i < factories.length; i++) {
- const factory = factories[i];
- result.push(factory());
- }
- return result;
- }
- /**
- * Creates a multi factory.
- */
- function multiFactory(factoryFn, index, isViewProvider, isComponent, f) {
- const factory = new NodeInjectorFactory(factoryFn, isViewProvider, ɵɵdirectiveInject);
- factory.multi = [];
- factory.index = index;
- factory.componentProviders = 0;
- multiFactoryAdd(factory, f, isComponent && !isViewProvider);
- return factory;
- }
- /**
- * This feature resolves the providers of a directive (or component),
- * and publish them into the DI system, making it visible to others for injection.
- *
- * For example:
- * ```ts
- * class ComponentWithProviders {
- * constructor(private greeter: GreeterDE) {}
- *
- * static ɵcmp = defineComponent({
- * type: ComponentWithProviders,
- * selectors: [['component-with-providers']],
- * factory: () => new ComponentWithProviders(directiveInject(GreeterDE as any)),
- * decls: 1,
- * vars: 1,
- * template: function(fs: RenderFlags, ctx: ComponentWithProviders) {
- * if (fs & RenderFlags.Create) {
- * ɵɵtext(0);
- * }
- * if (fs & RenderFlags.Update) {
- * ɵɵtextInterpolate(ctx.greeter.greet());
- * }
- * },
- * features: [ɵɵProvidersFeature([GreeterDE])]
- * });
- * }
- * ```
- *
- * @param definition
- *
- * @codeGenApi
- */
- function ɵɵProvidersFeature(providers, viewProviders = []) {
- return (definition) => {
- definition.providersResolver =
- (def, processProvidersFn) => {
- return providersResolver(def, //
- processProvidersFn ? processProvidersFn(providers) : providers, //
- viewProviders);
- };
- };
- }
- /**
- * Represents an instance of an `NgModule` created by an `NgModuleFactory`.
- * Provides access to the `NgModule` instance and related objects.
- *
- * @publicApi
- */
- class NgModuleRef$1 {
- }
- /**
- * @publicApi
- *
- * @deprecated
- * This class was mostly used as a part of ViewEngine-based JIT API and is no longer needed in Ivy
- * JIT mode. See [JIT API changes due to ViewEngine deprecation](guide/deprecations#jit-api-changes)
- * for additional context. Angular provides APIs that accept NgModule classes directly (such as
- * [PlatformRef.bootstrapModule](api/core/PlatformRef#bootstrapModule) and
- * [createNgModule](api/core/createNgModule)), consider switching to those APIs instead of
- * using factory-based ones.
- */
- class NgModuleFactory$1 {
- }
- /**
- * Returns a new NgModuleRef instance based on the NgModule class and parent injector provided.
- *
- * @param ngModule NgModule class.
- * @param parentInjector Optional injector instance to use as a parent for the module injector. If
- * not provided, `NullInjector` will be used instead.
- * @returns NgModuleRef that represents an NgModule instance.
- *
- * @publicApi
- */
- function createNgModule(ngModule, parentInjector) {
- return new NgModuleRef(ngModule, parentInjector ?? null, []);
- }
- /**
- * The `createNgModule` function alias for backwards-compatibility.
- * Please avoid using it directly and use `createNgModule` instead.
- *
- * @deprecated Use `createNgModule` instead.
- */
- const createNgModuleRef = createNgModule;
- class NgModuleRef extends NgModuleRef$1 {
- constructor(ngModuleType, _parent, additionalProviders) {
- super();
- this._parent = _parent;
- // tslint:disable-next-line:require-internal-with-underscore
- this._bootstrapComponents = [];
- this.destroyCbs = [];
- // When bootstrapping a module we have a dependency graph that looks like this:
- // ApplicationRef -> ComponentFactoryResolver -> NgModuleRef. The problem is that if the
- // module being resolved tries to inject the ComponentFactoryResolver, it'll create a
- // circular dependency which will result in a runtime error, because the injector doesn't
- // exist yet. We work around the issue by creating the ComponentFactoryResolver ourselves
- // and providing it, rather than letting the injector resolve it.
- this.componentFactoryResolver = new ComponentFactoryResolver(this);
- const ngModuleDef = getNgModuleDef(ngModuleType);
- ngDevMode &&
- assertDefined(ngModuleDef, `NgModule '${stringify(ngModuleType)}' is not a subtype of 'NgModuleType'.`);
- this._bootstrapComponents = maybeUnwrapFn$1(ngModuleDef.bootstrap);
- this._r3Injector = createInjectorWithoutInjectorInstances(ngModuleType, _parent, [
- { provide: NgModuleRef$1, useValue: this }, {
- provide: ComponentFactoryResolver$1,
- useValue: this.componentFactoryResolver
- },
- ...additionalProviders
- ], stringify(ngModuleType), new Set(['environment']));
- // We need to resolve the injector types separately from the injector creation, because
- // the module might be trying to use this ref in its constructor for DI which will cause a
- // circular error that will eventually error out, because the injector isn't created yet.
- this._r3Injector.resolveInjectorInitializers();
- this.instance = this._r3Injector.get(ngModuleType);
- }
- get injector() {
- return this._r3Injector;
- }
- destroy() {
- ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
- const injector = this._r3Injector;
- !injector.destroyed && injector.destroy();
- this.destroyCbs.forEach(fn => fn());
- this.destroyCbs = null;
- }
- onDestroy(callback) {
- ngDevMode && assertDefined(this.destroyCbs, 'NgModule already destroyed');
- this.destroyCbs.push(callback);
- }
- }
- class NgModuleFactory extends NgModuleFactory$1 {
- constructor(moduleType) {
- super();
- this.moduleType = moduleType;
- }
- create(parentInjector) {
- return new NgModuleRef(this.moduleType, parentInjector, []);
- }
- }
- function createNgModuleRefWithProviders(moduleType, parentInjector, additionalProviders) {
- return new NgModuleRef(moduleType, parentInjector, additionalProviders);
- }
- class EnvironmentNgModuleRefAdapter extends NgModuleRef$1 {
- constructor(config) {
- super();
- this.componentFactoryResolver = new ComponentFactoryResolver(this);
- this.instance = null;
- const injector = new R3Injector([
- ...config.providers,
- { provide: NgModuleRef$1, useValue: this },
- { provide: ComponentFactoryResolver$1, useValue: this.componentFactoryResolver },
- ], config.parent || getNullInjector(), config.debugName, new Set(['environment']));
- this.injector = injector;
- if (config.runEnvironmentInitializers) {
- injector.resolveInjectorInitializers();
- }
- }
- destroy() {
- this.injector.destroy();
- }
- onDestroy(callback) {
- this.injector.onDestroy(callback);
- }
- }
- /**
- * Create a new environment injector.
- *
- * Learn more about environment injectors in
- * [this guide](guide/standalone-components#environment-injectors).
- *
- * @param providers An array of providers.
- * @param parent A parent environment injector.
- * @param debugName An optional name for this injector instance, which will be used in error
- * messages.
- *
- * @publicApi
- */
- function createEnvironmentInjector(providers, parent, debugName = null) {
- const adapter = new EnvironmentNgModuleRefAdapter({ providers, parent, debugName, runEnvironmentInitializers: true });
- return adapter.injector;
- }
- /**
- * A service used by the framework to create instances of standalone injectors. Those injectors are
- * created on demand in case of dynamic component instantiation and contain ambient providers
- * collected from the imports graph rooted at a given standalone component.
- */
- class StandaloneService {
- constructor(_injector) {
- this._injector = _injector;
- this.cachedInjectors = new Map();
- }
- getOrCreateStandaloneInjector(componentDef) {
- if (!componentDef.standalone) {
- return null;
- }
- if (!this.cachedInjectors.has(componentDef)) {
- const providers = internalImportProvidersFrom(false, componentDef.type);
- const standaloneInjector = providers.length > 0 ?
- createEnvironmentInjector([providers], this._injector, `Standalone[${componentDef.type.name}]`) :
- null;
- this.cachedInjectors.set(componentDef, standaloneInjector);
- }
- return this.cachedInjectors.get(componentDef);
- }
- ngOnDestroy() {
- try {
- for (const injector of this.cachedInjectors.values()) {
- if (injector !== null) {
- injector.destroy();
- }
- }
- }
- finally {
- this.cachedInjectors.clear();
- }
- }
- /** @nocollapse */
- static { this.ɵprov = ɵɵdefineInjectable({
- token: StandaloneService,
- providedIn: 'environment',
- factory: () => new StandaloneService(ɵɵinject(EnvironmentInjector)),
- }); }
- }
- /**
- * A feature that acts as a setup code for the {@link StandaloneService}.
- *
- * The most important responsibility of this feature is to expose the "getStandaloneInjector"
- * function (an entry points to a standalone injector creation) on a component definition object. We
- * go through the features infrastructure to make sure that the standalone injector creation logic
- * is tree-shakable and not included in applications that don't use standalone components.
- *
- * @codeGenApi
- */
- function ɵɵStandaloneFeature(definition) {
- definition.getStandaloneInjector = (parentInjector) => {
- return parentInjector.get(StandaloneService).getOrCreateStandaloneInjector(definition);
- };
- }
- /**
- * Retrieves the component instance associated with a given DOM element.
- *
- * @usageNotes
- * Given the following DOM structure:
- *
- * ```html
- * <app-root>
- * <div>
- * <child-comp></child-comp>
- * </div>
- * </app-root>
- * ```
- *
- * Calling `getComponent` on `<child-comp>` will return the instance of `ChildComponent`
- * associated with this DOM element.
- *
- * Calling the function on `<app-root>` will return the `MyApp` instance.
- *
- *
- * @param element DOM element from which the component should be retrieved.
- * @returns Component instance associated with the element or `null` if there
- * is no component associated with it.
- *
- * @publicApi
- * @globalApi ng
- */
- function getComponent(element) {
- ngDevMode && assertDomElement(element);
- const context = getLContext(element);
- if (context === null)
- return null;
- if (context.component === undefined) {
- const lView = context.lView;
- if (lView === null) {
- return null;
- }
- context.component = getComponentAtNodeIndex(context.nodeIndex, lView);
- }
- return context.component;
- }
- /**
- * If inside an embedded view (e.g. `*ngIf` or `*ngFor`), retrieves the context of the embedded
- * view that the element is part of. Otherwise retrieves the instance of the component whose view
- * owns the element (in this case, the result is the same as calling `getOwningComponent`).
- *
- * @param element Element for which to get the surrounding component instance.
- * @returns Instance of the component that is around the element or null if the element isn't
- * inside any component.
- *
- * @publicApi
- * @globalApi ng
- */
- function getContext(element) {
- assertDomElement(element);
- const context = getLContext(element);
- const lView = context ? context.lView : null;
- return lView === null ? null : lView[CONTEXT];
- }
- /**
- * Retrieves the component instance whose view contains the DOM element.
- *
- * For example, if `<child-comp>` is used in the template of `<app-comp>`
- * (i.e. a `ViewChild` of `<app-comp>`), calling `getOwningComponent` on `<child-comp>`
- * would return `<app-comp>`.
- *
- * @param elementOrDir DOM element, component or directive instance
- * for which to retrieve the root components.
- * @returns Component instance whose view owns the DOM element or null if the element is not
- * part of a component view.
- *
- * @publicApi
- * @globalApi ng
- */
- function getOwningComponent(elementOrDir) {
- const context = getLContext(elementOrDir);
- let lView = context ? context.lView : null;
- if (lView === null)
- return null;
- let parent;
- while (lView[TVIEW].type === 2 /* TViewType.Embedded */ && (parent = getLViewParent(lView))) {
- lView = parent;
- }
- return lView[FLAGS] & 512 /* LViewFlags.IsRoot */ ? null : lView[CONTEXT];
- }
- /**
- * Retrieves all root components associated with a DOM element, directive or component instance.
- * Root components are those which have been bootstrapped by Angular.
- *
- * @param elementOrDir DOM element, component or directive instance
- * for which to retrieve the root components.
- * @returns Root components associated with the target object.
- *
- * @publicApi
- * @globalApi ng
- */
- function getRootComponents(elementOrDir) {
- const lView = readPatchedLView(elementOrDir);
- return lView !== null ? [getRootContext(lView)] : [];
- }
- /**
- * Retrieves an `Injector` associated with an element, component or directive instance.
- *
- * @param elementOrDir DOM element, component or directive instance for which to
- * retrieve the injector.
- * @returns Injector associated with the element, component or directive instance.
- *
- * @publicApi
- * @globalApi ng
- */
- function getInjector(elementOrDir) {
- const context = getLContext(elementOrDir);
- const lView = context ? context.lView : null;
- if (lView === null)
- return Injector.NULL;
- const tNode = lView[TVIEW].data[context.nodeIndex];
- return new NodeInjector(tNode, lView);
- }
- /**
- * Retrieve a set of injection tokens at a given DOM node.
- *
- * @param element Element for which the injection tokens should be retrieved.
- */
- function getInjectionTokens(element) {
- const context = getLContext(element);
- const lView = context ? context.lView : null;
- if (lView === null)
- return [];
- const tView = lView[TVIEW];
- const tNode = tView.data[context.nodeIndex];
- const providerTokens = [];
- const startIndex = tNode.providerIndexes & 1048575 /* TNodeProviderIndexes.ProvidersStartIndexMask */;
- const endIndex = tNode.directiveEnd;
- for (let i = startIndex; i < endIndex; i++) {
- let value = tView.data[i];
- if (isDirectiveDefHack(value)) {
- // The fact that we sometimes store Type and sometimes DirectiveDef in this location is a
- // design flaw. We should always store same type so that we can be monomorphic. The issue
- // is that for Components/Directives we store the def instead the type. The correct behavior
- // is that we should always be storing injectable type in this location.
- value = value.type;
- }
- providerTokens.push(value);
- }
- return providerTokens;
- }
- /**
- * Retrieves directive instances associated with a given DOM node. Does not include
- * component instances.
- *
- * @usageNotes
- * Given the following DOM structure:
- *
- * ```html
- * <app-root>
- * <button my-button></button>
- * <my-comp></my-comp>
- * </app-root>
- * ```
- *
- * Calling `getDirectives` on `<button>` will return an array with an instance of the `MyButton`
- * directive that is associated with the DOM node.
- *
- * Calling `getDirectives` on `<my-comp>` will return an empty array.
- *
- * @param node DOM node for which to get the directives.
- * @returns Array of directives associated with the node.
- *
- * @publicApi
- * @globalApi ng
- */
- function getDirectives(node) {
- // Skip text nodes because we can't have directives associated with them.
- if (node instanceof Text) {
- return [];
- }
- const context = getLContext(node);
- const lView = context ? context.lView : null;
- if (lView === null) {
- return [];
- }
- const tView = lView[TVIEW];
- const nodeIndex = context.nodeIndex;
- if (!tView?.data[nodeIndex]) {
- return [];
- }
- if (context.directives === undefined) {
- context.directives = getDirectivesAtNodeIndex(nodeIndex, lView);
- }
- // The `directives` in this case are a named array called `LComponentView`. Clone the
- // result so we don't expose an internal data structure in the user's console.
- return context.directives === null ? [] : [...context.directives];
- }
- /**
- * Returns the debug (partial) metadata for a particular directive or component instance.
- * The function accepts an instance of a directive or component and returns the corresponding
- * metadata.
- *
- * @param directiveOrComponentInstance Instance of a directive or component
- * @returns metadata of the passed directive or component
- *
- * @publicApi
- * @globalApi ng
- */
- function getDirectiveMetadata(directiveOrComponentInstance) {
- const { constructor } = directiveOrComponentInstance;
- if (!constructor) {
- throw new Error('Unable to find the instance constructor');
- }
- // In case a component inherits from a directive, we may have component and directive metadata
- // To ensure we don't get the metadata of the directive, we want to call `getComponentDef` first.
- const componentDef = getComponentDef$1(constructor);
- if (componentDef) {
- return {
- inputs: componentDef.inputs,
- outputs: componentDef.outputs,
- encapsulation: componentDef.encapsulation,
- changeDetection: componentDef.onPush ? ChangeDetectionStrategy.OnPush :
- ChangeDetectionStrategy.Default
- };
- }
- const directiveDef = getDirectiveDef(constructor);
- if (directiveDef) {
- return { inputs: directiveDef.inputs, outputs: directiveDef.outputs };
- }
- return null;
- }
- /**
- * Retrieve map of local references.
- *
- * The references are retrieved as a map of local reference name to element or directive instance.
- *
- * @param target DOM element, component or directive instance for which to retrieve
- * the local references.
- */
- function getLocalRefs(target) {
- const context = getLContext(target);
- if (context === null)
- return {};
- if (context.localRefs === undefined) {
- const lView = context.lView;
- if (lView === null) {
- return {};
- }
- context.localRefs = discoverLocalRefs(lView, context.nodeIndex);
- }
- return context.localRefs || {};
- }
- /**
- * Retrieves the host element of a component or directive instance.
- * The host element is the DOM element that matched the selector of the directive.
- *
- * @param componentOrDirective Component or directive instance for which the host
- * element should be retrieved.
- * @returns Host element of the target.
- *
- * @publicApi
- * @globalApi ng
- */
- function getHostElement(componentOrDirective) {
- return getLContext(componentOrDirective).native;
- }
- /**
- * Retrieves the rendered text for a given component.
- *
- * This function retrieves the host element of a component and
- * and then returns the `textContent` for that element. This implies
- * that the text returned will include re-projected content of
- * the component as well.
- *
- * @param component The component to return the content text for.
- */
- function getRenderedText(component) {
- const hostElement = getHostElement(component);
- return hostElement.textContent || '';
- }
- /**
- * Retrieves a list of event listeners associated with a DOM element. The list does include host
- * listeners, but it does not include event listeners defined outside of the Angular context
- * (e.g. through `addEventListener`).
- *
- * @usageNotes
- * Given the following DOM structure:
- *
- * ```html
- * <app-root>
- * <div (click)="doSomething()"></div>
- * </app-root>
- * ```
- *
- * Calling `getListeners` on `<div>` will return an object that looks as follows:
- *
- * ```ts
- * {
- * name: 'click',
- * element: <div>,
- * callback: () => doSomething(),
- * useCapture: false
- * }
- * ```
- *
- * @param element Element for which the DOM listeners should be retrieved.
- * @returns Array of event listeners on the DOM element.
- *
- * @publicApi
- * @globalApi ng
- */
- function getListeners(element) {
- ngDevMode && assertDomElement(element);
- const lContext = getLContext(element);
- const lView = lContext === null ? null : lContext.lView;
- if (lView === null)
- return [];
- const tView = lView[TVIEW];
- const lCleanup = lView[CLEANUP];
- const tCleanup = tView.cleanup;
- const listeners = [];
- if (tCleanup && lCleanup) {
- for (let i = 0; i < tCleanup.length;) {
- const firstParam = tCleanup[i++];
- const secondParam = tCleanup[i++];
- if (typeof firstParam === 'string') {
- const name = firstParam;
- const listenerElement = unwrapRNode(lView[secondParam]);
- const callback = lCleanup[tCleanup[i++]];
- const useCaptureOrIndx = tCleanup[i++];
- // if useCaptureOrIndx is boolean then report it as is.
- // if useCaptureOrIndx is positive number then it in unsubscribe method
- // if useCaptureOrIndx is negative number then it is a Subscription
- const type = (typeof useCaptureOrIndx === 'boolean' || useCaptureOrIndx >= 0) ? 'dom' : 'output';
- const useCapture = typeof useCaptureOrIndx === 'boolean' ? useCaptureOrIndx : false;
- if (element == listenerElement) {
- listeners.push({ element, name, callback, useCapture, type });
- }
- }
- }
- }
- listeners.sort(sortListeners);
- return listeners;
- }
- function sortListeners(a, b) {
- if (a.name == b.name)
- return 0;
- return a.name < b.name ? -1 : 1;
- }
- /**
- * This function should not exist because it is megamorphic and only mostly correct.
- *
- * See call site for more info.
- */
- function isDirectiveDefHack(obj) {
- return obj.type !== undefined && obj.declaredInputs !== undefined &&
- obj.findHostDirectiveDefs !== undefined;
- }
- /**
- * Retrieve the component `LView` from component/element.
- *
- * NOTE: `LView` is a private and should not be leaked outside.
- * Don't export this method to `ng.*` on window.
- *
- * @param target DOM element or component instance for which to retrieve the LView.
- */
- function getComponentLView(target) {
- const lContext = getLContext(target);
- const nodeIndx = lContext.nodeIndex;
- const lView = lContext.lView;
- ngDevMode && assertLView(lView);
- const componentLView = lView[nodeIndx];
- ngDevMode && assertLView(componentLView);
- return componentLView;
- }
- /** Asserts that a value is a DOM Element. */
- function assertDomElement(value) {
- if (typeof Element !== 'undefined' && !(value instanceof Element)) {
- throw new Error('Expecting instance of DOM Element');
- }
- }
- /**
- * Adds decorator, constructor, and property metadata to a given type via static metadata fields
- * on the type.
- *
- * These metadata fields can later be read with Angular's `ReflectionCapabilities` API.
- *
- * Calls to `setClassMetadata` can be guarded by ngDevMode, resulting in the metadata assignments
- * being tree-shaken away during production builds.
- */
- function setClassMetadata(type, decorators, ctorParameters, propDecorators) {
- return noSideEffects(() => {
- const clazz = type;
- if (decorators !== null) {
- if (clazz.hasOwnProperty('decorators') && clazz.decorators !== undefined) {
- clazz.decorators.push(...decorators);
- }
- else {
- clazz.decorators = decorators;
- }
- }
- if (ctorParameters !== null) {
- // Rather than merging, clobber the existing parameters. If other projects exist which
- // use tsickle-style annotations and reflect over them in the same way, this could
- // cause issues, but that is vanishingly unlikely.
- clazz.ctorParameters = ctorParameters;
- }
- if (propDecorators !== null) {
- // The property decorator objects are merged as it is possible different fields have
- // different decorator types. Decorators on individual fields are not merged, as it's
- // also incredibly unlikely that a field will be decorated both with an Angular
- // decorator and a non-Angular decorator that's also been downleveled.
- if (clazz.hasOwnProperty('propDecorators') && clazz.propDecorators !== undefined) {
- clazz.propDecorators = { ...clazz.propDecorators, ...propDecorators };
- }
- else {
- clazz.propDecorators = propDecorators;
- }
- }
- });
- }
- /**
- * Bindings for pure functions are stored after regular bindings.
- *
- * |-------decls------|---------vars---------| |----- hostVars (dir1) ------|
- * ------------------------------------------------------------------------------------------
- * | nodes/refs/pipes | bindings | fn slots | injector | dir1 | host bindings | host slots |
- * ------------------------------------------------------------------------------------------
- * ^ ^
- * TView.bindingStartIndex TView.expandoStartIndex
- *
- * Pure function instructions are given an offset from the binding root. Adding the offset to the
- * binding root gives the first index where the bindings are stored. In component views, the binding
- * root is the bindingStartIndex. In host bindings, the binding root is the expandoStartIndex +
- * any directive instances + any hostVars in directives evaluated before it.
- *
- * See VIEW_DATA.md for more information about host binding resolution.
- */
- /**
- * If the value hasn't been saved, calls the pure function to store and return the
- * value. If it has been saved, returns the saved value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn Function that returns a value
- * @param thisArg Optional calling context of pureFn
- * @returns value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction0(slotOffset, pureFn, thisArg) {
- const bindingIndex = getBindingRoot() + slotOffset;
- const lView = getLView();
- return lView[bindingIndex] === NO_CHANGE ?
- updateBinding(lView, bindingIndex, thisArg ? pureFn.call(thisArg) : pureFn()) :
- getBinding(lView, bindingIndex);
- }
- /**
- * If the value of the provided exp has changed, calls the pure function to return
- * an updated value. Or if the value has not changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn Function that returns an updated value
- * @param exp Updated expression value
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction1(slotOffset, pureFn, exp, thisArg) {
- return pureFunction1Internal(getLView(), getBindingRoot(), slotOffset, pureFn, exp, thisArg);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction2(slotOffset, pureFn, exp1, exp2, thisArg) {
- return pureFunction2Internal(getLView(), getBindingRoot(), slotOffset, pureFn, exp1, exp2, thisArg);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction3(slotOffset, pureFn, exp1, exp2, exp3, thisArg) {
- return pureFunction3Internal(getLView(), getBindingRoot(), slotOffset, pureFn, exp1, exp2, exp3, thisArg);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param exp4
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction4(slotOffset, pureFn, exp1, exp2, exp3, exp4, thisArg) {
- return pureFunction4Internal(getLView(), getBindingRoot(), slotOffset, pureFn, exp1, exp2, exp3, exp4, thisArg);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param exp4
- * @param exp5
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction5(slotOffset, pureFn, exp1, exp2, exp3, exp4, exp5, thisArg) {
- const bindingIndex = getBindingRoot() + slotOffset;
- const lView = getLView();
- const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
- return bindingUpdated(lView, bindingIndex + 4, exp5) || different ?
- updateBinding(lView, bindingIndex + 5, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) :
- pureFn(exp1, exp2, exp3, exp4, exp5)) :
- getBinding(lView, bindingIndex + 5);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param exp4
- * @param exp5
- * @param exp6
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction6(slotOffset, pureFn, exp1, exp2, exp3, exp4, exp5, exp6, thisArg) {
- const bindingIndex = getBindingRoot() + slotOffset;
- const lView = getLView();
- const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
- return bindingUpdated2(lView, bindingIndex + 4, exp5, exp6) || different ?
- updateBinding(lView, bindingIndex + 6, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) :
- pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) :
- getBinding(lView, bindingIndex + 6);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param exp4
- * @param exp5
- * @param exp6
- * @param exp7
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction7(slotOffset, pureFn, exp1, exp2, exp3, exp4, exp5, exp6, exp7, thisArg) {
- const bindingIndex = getBindingRoot() + slotOffset;
- const lView = getLView();
- let different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
- return bindingUpdated3(lView, bindingIndex + 4, exp5, exp6, exp7) || different ?
- updateBinding(lView, bindingIndex + 7, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) :
- pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) :
- getBinding(lView, bindingIndex + 7);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param exp4
- * @param exp5
- * @param exp6
- * @param exp7
- * @param exp8
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunction8(slotOffset, pureFn, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8, thisArg) {
- const bindingIndex = getBindingRoot() + slotOffset;
- const lView = getLView();
- const different = bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4);
- return bindingUpdated4(lView, bindingIndex + 4, exp5, exp6, exp7, exp8) || different ?
- updateBinding(lView, bindingIndex + 8, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) :
- pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) :
- getBinding(lView, bindingIndex + 8);
- }
- /**
- * pureFunction instruction that can support any number of bindings.
- *
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn A pure function that takes binding values and builds an object or array
- * containing those values.
- * @param exps An array of binding values
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- * @codeGenApi
- */
- function ɵɵpureFunctionV(slotOffset, pureFn, exps, thisArg) {
- return pureFunctionVInternal(getLView(), getBindingRoot(), slotOffset, pureFn, exps, thisArg);
- }
- /**
- * Results of a pure function invocation are stored in LView in a dedicated slot that is initialized
- * to NO_CHANGE. In rare situations a pure pipe might throw an exception on the very first
- * invocation and not produce any valid results. In this case LView would keep holding the NO_CHANGE
- * value. The NO_CHANGE is not something that we can use in expressions / bindings thus we convert
- * it to `undefined`.
- */
- function getPureFunctionReturnValue(lView, returnValueIndex) {
- ngDevMode && assertIndexInRange(lView, returnValueIndex);
- const lastReturnValue = lView[returnValueIndex];
- return lastReturnValue === NO_CHANGE ? undefined : lastReturnValue;
- }
- /**
- * If the value of the provided exp has changed, calls the pure function to return
- * an updated value. Or if the value has not changed, returns cached value.
- *
- * @param lView LView in which the function is being executed.
- * @param bindingRoot Binding root index.
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn Function that returns an updated value
- * @param exp Updated expression value
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- */
- function pureFunction1Internal(lView, bindingRoot, slotOffset, pureFn, exp, thisArg) {
- const bindingIndex = bindingRoot + slotOffset;
- return bindingUpdated(lView, bindingIndex, exp) ?
- updateBinding(lView, bindingIndex + 1, thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) :
- getPureFunctionReturnValue(lView, bindingIndex + 1);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param lView LView in which the function is being executed.
- * @param bindingRoot Binding root index.
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- */
- function pureFunction2Internal(lView, bindingRoot, slotOffset, pureFn, exp1, exp2, thisArg) {
- const bindingIndex = bindingRoot + slotOffset;
- return bindingUpdated2(lView, bindingIndex, exp1, exp2) ?
- updateBinding(lView, bindingIndex + 2, thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) :
- getPureFunctionReturnValue(lView, bindingIndex + 2);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param lView LView in which the function is being executed.
- * @param bindingRoot Binding root index.
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- */
- function pureFunction3Internal(lView, bindingRoot, slotOffset, pureFn, exp1, exp2, exp3, thisArg) {
- const bindingIndex = bindingRoot + slotOffset;
- return bindingUpdated3(lView, bindingIndex, exp1, exp2, exp3) ?
- updateBinding(lView, bindingIndex + 3, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) :
- getPureFunctionReturnValue(lView, bindingIndex + 3);
- }
- /**
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param lView LView in which the function is being executed.
- * @param bindingRoot Binding root index.
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn
- * @param exp1
- * @param exp2
- * @param exp3
- * @param exp4
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- *
- */
- function pureFunction4Internal(lView, bindingRoot, slotOffset, pureFn, exp1, exp2, exp3, exp4, thisArg) {
- const bindingIndex = bindingRoot + slotOffset;
- return bindingUpdated4(lView, bindingIndex, exp1, exp2, exp3, exp4) ?
- updateBinding(lView, bindingIndex + 4, thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) :
- getPureFunctionReturnValue(lView, bindingIndex + 4);
- }
- /**
- * pureFunction instruction that can support any number of bindings.
- *
- * If the value of any provided exp has changed, calls the pure function to return
- * an updated value. Or if no values have changed, returns cached value.
- *
- * @param lView LView in which the function is being executed.
- * @param bindingRoot Binding root index.
- * @param slotOffset the offset from binding root to the reserved slot
- * @param pureFn A pure function that takes binding values and builds an object or array
- * containing those values.
- * @param exps An array of binding values
- * @param thisArg Optional calling context of pureFn
- * @returns Updated or cached value
- */
- function pureFunctionVInternal(lView, bindingRoot, slotOffset, pureFn, exps, thisArg) {
- let bindingIndex = bindingRoot + slotOffset;
- let different = false;
- for (let i = 0; i < exps.length; i++) {
- bindingUpdated(lView, bindingIndex++, exps[i]) && (different = true);
- }
- return different ? updateBinding(lView, bindingIndex, pureFn.apply(thisArg, exps)) :
- getPureFunctionReturnValue(lView, bindingIndex);
- }
- /**
- * Create a pipe.
- *
- * @param index Pipe index where the pipe will be stored.
- * @param pipeName The name of the pipe
- * @returns T the instance of the pipe.
- *
- * @codeGenApi
- */
- function ɵɵpipe(index, pipeName) {
- const tView = getTView();
- let pipeDef;
- const adjustedIndex = index + HEADER_OFFSET;
- if (tView.firstCreatePass) {
- // The `getPipeDef` throws if a pipe with a given name is not found
- // (so we use non-null assertion below).
- pipeDef = getPipeDef(pipeName, tView.pipeRegistry);
- tView.data[adjustedIndex] = pipeDef;
- if (pipeDef.onDestroy) {
- (tView.destroyHooks ??= []).push(adjustedIndex, pipeDef.onDestroy);
- }
- }
- else {
- pipeDef = tView.data[adjustedIndex];
- }
- const pipeFactory = pipeDef.factory || (pipeDef.factory = getFactoryDef(pipeDef.type, true));
- let previousInjectorProfilerContext;
- if (ngDevMode) {
- previousInjectorProfilerContext = setInjectorProfilerContext({
- injector: new NodeInjector(getCurrentTNode(), getLView()),
- token: pipeDef.type
- });
- }
- const previousInjectImplementation = setInjectImplementation(ɵɵdirectiveInject);
- try {
- // DI for pipes is supposed to behave like directives when placed on a component
- // host node, which means that we have to disable access to `viewProviders`.
- const previousIncludeViewProviders = setIncludeViewProviders(false);
- const pipeInstance = pipeFactory();
- setIncludeViewProviders(previousIncludeViewProviders);
- store(tView, getLView(), adjustedIndex, pipeInstance);
- return pipeInstance;
- }
- finally {
- // we have to restore the injector implementation in finally, just in case the creation of the
- // pipe throws an error.
- setInjectImplementation(previousInjectImplementation);
- ngDevMode && setInjectorProfilerContext(previousInjectorProfilerContext);
- }
- }
- /**
- * Searches the pipe registry for a pipe with the given name. If one is found,
- * returns the pipe. Otherwise, an error is thrown because the pipe cannot be resolved.
- *
- * @param name Name of pipe to resolve
- * @param registry Full list of available pipes
- * @returns Matching PipeDef
- */
- function getPipeDef(name, registry) {
- if (registry) {
- if (ngDevMode) {
- const pipes = registry.filter(pipe => pipe.name === name);
- // TODO: Throw an error in the next major
- if (pipes.length > 1) {
- console.warn(formatRuntimeError(313 /* RuntimeErrorCode.MULTIPLE_MATCHING_PIPES */, getMultipleMatchingPipesMessage(name)));
- }
- }
- for (let i = registry.length - 1; i >= 0; i--) {
- const pipeDef = registry[i];
- if (name === pipeDef.name) {
- return pipeDef;
- }
- }
- }
- if (ngDevMode) {
- throw new RuntimeError(-302 /* RuntimeErrorCode.PIPE_NOT_FOUND */, getPipeNotFoundErrorMessage(name));
- }
- }
- /**
- * Generates a helpful error message for the user when multiple pipes match the name.
- *
- * @param name Name of the pipe
- * @returns The error message
- */
- function getMultipleMatchingPipesMessage(name) {
- const lView = getLView();
- const declarationLView = lView[DECLARATION_COMPONENT_VIEW];
- const context = declarationLView[CONTEXT];
- const hostIsStandalone = isHostComponentStandalone(lView);
- const componentInfoMessage = context ? ` in the '${context.constructor.name}' component` : '';
- const verifyMessage = `check ${hostIsStandalone ? '\'@Component.imports\' of this component' :
- 'the imports of this module'}`;
- const errorMessage = `Multiple pipes match the name \`${name}\`${componentInfoMessage}. ${verifyMessage}`;
- return errorMessage;
- }
- /**
- * Generates a helpful error message for the user when a pipe is not found.
- *
- * @param name Name of the missing pipe
- * @returns The error message
- */
- function getPipeNotFoundErrorMessage(name) {
- const lView = getLView();
- const declarationLView = lView[DECLARATION_COMPONENT_VIEW];
- const context = declarationLView[CONTEXT];
- const hostIsStandalone = isHostComponentStandalone(lView);
- const componentInfoMessage = context ? ` in the '${context.constructor.name}' component` : '';
- const verifyMessage = `Verify that it is ${hostIsStandalone ? 'included in the \'@Component.imports\' of this component' :
- 'declared or imported in this module'}`;
- const errorMessage = `The pipe '${name}' could not be found${componentInfoMessage}. ${verifyMessage}`;
- return errorMessage;
- }
- /**
- * Invokes a pipe with 1 arguments.
- *
- * This instruction acts as a guard to {@link PipeTransform#transform} invoking
- * the pipe only when an input to the pipe changes.
- *
- * @param index Pipe index where the pipe was stored on creation.
- * @param slotOffset the offset in the reserved slot space
- * @param v1 1st argument to {@link PipeTransform#transform}.
- *
- * @codeGenApi
- */
- function ɵɵpipeBind1(index, slotOffset, v1) {
- const adjustedIndex = index + HEADER_OFFSET;
- const lView = getLView();
- const pipeInstance = load(lView, adjustedIndex);
- return isPure(lView, adjustedIndex) ?
- pureFunction1Internal(lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, pipeInstance) :
- pipeInstance.transform(v1);
- }
- /**
- * Invokes a pipe with 2 arguments.
- *
- * This instruction acts as a guard to {@link PipeTransform#transform} invoking
- * the pipe only when an input to the pipe changes.
- *
- * @param index Pipe index where the pipe was stored on creation.
- * @param slotOffset the offset in the reserved slot space
- * @param v1 1st argument to {@link PipeTransform#transform}.
- * @param v2 2nd argument to {@link PipeTransform#transform}.
- *
- * @codeGenApi
- */
- function ɵɵpipeBind2(index, slotOffset, v1, v2) {
- const adjustedIndex = index + HEADER_OFFSET;
- const lView = getLView();
- const pipeInstance = load(lView, adjustedIndex);
- return isPure(lView, adjustedIndex) ?
- pureFunction2Internal(lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, v2, pipeInstance) :
- pipeInstance.transform(v1, v2);
- }
- /**
- * Invokes a pipe with 3 arguments.
- *
- * This instruction acts as a guard to {@link PipeTransform#transform} invoking
- * the pipe only when an input to the pipe changes.
- *
- * @param index Pipe index where the pipe was stored on creation.
- * @param slotOffset the offset in the reserved slot space
- * @param v1 1st argument to {@link PipeTransform#transform}.
- * @param v2 2nd argument to {@link PipeTransform#transform}.
- * @param v3 4rd argument to {@link PipeTransform#transform}.
- *
- * @codeGenApi
- */
- function ɵɵpipeBind3(index, slotOffset, v1, v2, v3) {
- const adjustedIndex = index + HEADER_OFFSET;
- const lView = getLView();
- const pipeInstance = load(lView, adjustedIndex);
- return isPure(lView, adjustedIndex) ?
- pureFunction3Internal(lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, v2, v3, pipeInstance) :
- pipeInstance.transform(v1, v2, v3);
- }
- /**
- * Invokes a pipe with 4 arguments.
- *
- * This instruction acts as a guard to {@link PipeTransform#transform} invoking
- * the pipe only when an input to the pipe changes.
- *
- * @param index Pipe index where the pipe was stored on creation.
- * @param slotOffset the offset in the reserved slot space
- * @param v1 1st argument to {@link PipeTransform#transform}.
- * @param v2 2nd argument to {@link PipeTransform#transform}.
- * @param v3 3rd argument to {@link PipeTransform#transform}.
- * @param v4 4th argument to {@link PipeTransform#transform}.
- *
- * @codeGenApi
- */
- function ɵɵpipeBind4(index, slotOffset, v1, v2, v3, v4) {
- const adjustedIndex = index + HEADER_OFFSET;
- const lView = getLView();
- const pipeInstance = load(lView, adjustedIndex);
- return isPure(lView, adjustedIndex) ? pureFunction4Internal(lView, getBindingRoot(), slotOffset, pipeInstance.transform, v1, v2, v3, v4, pipeInstance) :
- pipeInstance.transform(v1, v2, v3, v4);
- }
- /**
- * Invokes a pipe with variable number of arguments.
- *
- * This instruction acts as a guard to {@link PipeTransform#transform} invoking
- * the pipe only when an input to the pipe changes.
- *
- * @param index Pipe index where the pipe was stored on creation.
- * @param slotOffset the offset in the reserved slot space
- * @param values Array of arguments to pass to {@link PipeTransform#transform} method.
- *
- * @codeGenApi
- */
- function ɵɵpipeBindV(index, slotOffset, values) {
- const adjustedIndex = index + HEADER_OFFSET;
- const lView = getLView();
- const pipeInstance = load(lView, adjustedIndex);
- return isPure(lView, adjustedIndex) ?
- pureFunctionVInternal(lView, getBindingRoot(), slotOffset, pipeInstance.transform, values, pipeInstance) :
- pipeInstance.transform.apply(pipeInstance, values);
- }
- function isPure(lView, index) {
- return lView[TVIEW].data[index].pure;
- }
- function symbolIterator() {
- // @ts-expect-error accessing a private member
- return this._results[Symbol.iterator]();
- }
- /**
- * An unmodifiable list of items that Angular keeps up to date when the state
- * of the application changes.
- *
- * The type of object that {@link ViewChildren}, {@link ContentChildren}, and {@link QueryList}
- * provide.
- *
- * Implements an iterable interface, therefore it can be used in both ES6
- * javascript `for (var i of items)` loops as well as in Angular templates with
- * `*ngFor="let i of myList"`.
- *
- * Changes can be observed by subscribing to the changes `Observable`.
- *
- * NOTE: In the future this class will implement an `Observable` interface.
- *
- * @usageNotes
- * ### Example
- * ```typescript
- * @Component({...})
- * class Container {
- * @ViewChildren(Item) items:QueryList<Item>;
- * }
- * ```
- *
- * @publicApi
- */
- class QueryList {
- static { Symbol.iterator; }
- /**
- * Returns `Observable` of `QueryList` notifying the subscriber of changes.
- */
- get changes() {
- return this._changes || (this._changes = new EventEmitter());
- }
- /**
- * @param emitDistinctChangesOnly Whether `QueryList.changes` should fire only when actual change
- * has occurred. Or if it should fire when query is recomputed. (recomputing could resolve in
- * the same result)
- */
- constructor(_emitDistinctChangesOnly = false) {
- this._emitDistinctChangesOnly = _emitDistinctChangesOnly;
- this.dirty = true;
- this._results = [];
- this._changesDetected = false;
- this._changes = null;
- this.length = 0;
- this.first = undefined;
- this.last = undefined;
- // This function should be declared on the prototype, but doing so there will cause the class
- // declaration to have side-effects and become not tree-shakable. For this reason we do it in
- // the constructor.
- // [Symbol.iterator](): Iterator<T> { ... }
- const proto = QueryList.prototype;
- if (!proto[Symbol.iterator])
- proto[Symbol.iterator] = symbolIterator;
- }
- /**
- * Returns the QueryList entry at `index`.
- */
- get(index) {
- return this._results[index];
- }
- /**
- * See
- * [Array.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- */
- map(fn) {
- return this._results.map(fn);
- }
- filter(fn) {
- return this._results.filter(fn);
- }
- /**
- * See
- * [Array.find](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
- */
- find(fn) {
- return this._results.find(fn);
- }
- /**
- * See
- * [Array.reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)
- */
- reduce(fn, init) {
- return this._results.reduce(fn, init);
- }
- /**
- * See
- * [Array.forEach](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)
- */
- forEach(fn) {
- this._results.forEach(fn);
- }
- /**
- * See
- * [Array.some](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)
- */
- some(fn) {
- return this._results.some(fn);
- }
- /**
- * Returns a copy of the internal results list as an Array.
- */
- toArray() {
- return this._results.slice();
- }
- toString() {
- return this._results.toString();
- }
- /**
- * Updates the stored data of the query list, and resets the `dirty` flag to `false`, so that
- * on change detection, it will not notify of changes to the queries, unless a new change
- * occurs.
- *
- * @param resultsTree The query results to store
- * @param identityAccessor Optional function for extracting stable object identity from a value
- * in the array. This function is executed for each element of the query result list while
- * comparing current query list with the new one (provided as a first argument of the `reset`
- * function) to detect if the lists are different. If the function is not provided, elements
- * are compared as is (without any pre-processing).
- */
- reset(resultsTree, identityAccessor) {
- // Cast to `QueryListInternal` so that we can mutate fields which are readonly for the usage of
- // QueryList (but not for QueryList itself.)
- const self = this;
- self.dirty = false;
- const newResultFlat = flatten$1(resultsTree);
- if (this._changesDetected = !arrayEquals(self._results, newResultFlat, identityAccessor)) {
- self._results = newResultFlat;
- self.length = newResultFlat.length;
- self.last = newResultFlat[this.length - 1];
- self.first = newResultFlat[0];
- }
- }
- /**
- * Triggers a change event by emitting on the `changes` {@link EventEmitter}.
- */
- notifyOnChanges() {
- if (this._changes && (this._changesDetected || !this._emitDistinctChangesOnly))
- this._changes.emit(this);
- }
- /** internal */
- setDirty() {
- this.dirty = true;
- }
- /** internal */
- destroy() {
- this.changes.complete();
- this.changes.unsubscribe();
- }
- }
- function createAndRenderEmbeddedLView(declarationLView, templateTNode, context, options) {
- const embeddedTView = templateTNode.tView;
- ngDevMode && assertDefined(embeddedTView, 'TView must be defined for a template node.');
- ngDevMode && assertTNodeForLView(templateTNode, declarationLView);
- // Embedded views follow the change detection strategy of the view they're declared in.
- const isSignalView = declarationLView[FLAGS] & 4096 /* LViewFlags.SignalView */;
- const viewFlags = isSignalView ? 4096 /* LViewFlags.SignalView */ : 16 /* LViewFlags.CheckAlways */;
- const embeddedLView = createLView(declarationLView, embeddedTView, context, viewFlags, null, templateTNode, null, null, null, options?.injector ?? null, options?.hydrationInfo ?? null);
- const declarationLContainer = declarationLView[templateTNode.index];
- ngDevMode && assertLContainer(declarationLContainer);
- embeddedLView[DECLARATION_LCONTAINER] = declarationLContainer;
- const declarationViewLQueries = declarationLView[QUERIES];
- if (declarationViewLQueries !== null) {
- embeddedLView[QUERIES] = declarationViewLQueries.createEmbeddedView(embeddedTView);
- }
- // execute creation mode of a view
- renderView(embeddedTView, embeddedLView, context);
- return embeddedLView;
- }
- function getLViewFromLContainer(lContainer, index) {
- const adjustedIndex = CONTAINER_HEADER_OFFSET + index;
- // avoid reading past the array boundaries
- if (adjustedIndex < lContainer.length) {
- const lView = lContainer[adjustedIndex];
- ngDevMode && assertLView(lView);
- return lView;
- }
- return undefined;
- }
- function addLViewToLContainer(lContainer, lView, index, addToDOM = true) {
- const tView = lView[TVIEW];
- // insert to the view tree so the new view can be change-detected
- insertView(tView, lView, lContainer, index);
- // insert to the view to the DOM tree
- if (addToDOM) {
- const beforeNode = getBeforeNodeForView(index, lContainer);
- const renderer = lView[RENDERER];
- const parentRNode = nativeParentNode(renderer, lContainer[NATIVE]);
- if (parentRNode !== null) {
- addViewToDOM(tView, lContainer[T_HOST], renderer, lView, parentRNode, beforeNode);
- }
- }
- }
- function removeLViewFromLContainer(lContainer, index) {
- const lView = detachView(lContainer, index);
- if (lView !== undefined) {
- destroyLView(lView[TVIEW], lView);
- }
- return lView;
- }
- /**
- * Represents an embedded template that can be used to instantiate embedded views.
- * To instantiate embedded views based on a template, use the `ViewContainerRef`
- * method `createEmbeddedView()`.
- *
- * Access a `TemplateRef` instance by placing a directive on an `<ng-template>`
- * element (or directive prefixed with `*`). The `TemplateRef` for the embedded view
- * is injected into the constructor of the directive,
- * using the `TemplateRef` token.
- *
- * You can also use a `Query` to find a `TemplateRef` associated with
- * a component or a directive.
- *
- * @see {@link ViewContainerRef}
- * @see [Navigate the Component Tree with DI](guide/dependency-injection-navtree)
- *
- * @publicApi
- */
- class TemplateRef {
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ELEMENT_ID__ = injectTemplateRef; }
- }
- const ViewEngineTemplateRef = TemplateRef;
- // TODO(alxhub): combine interface and implementation. Currently this is challenging since something
- // in g3 depends on them being separate.
- const R3TemplateRef = class TemplateRef extends ViewEngineTemplateRef {
- constructor(_declarationLView, _declarationTContainer, elementRef) {
- super();
- this._declarationLView = _declarationLView;
- this._declarationTContainer = _declarationTContainer;
- this.elementRef = elementRef;
- }
- /**
- * Returns an `ssrId` associated with a TView, which was used to
- * create this instance of the `TemplateRef`.
- *
- * @internal
- */
- get ssrId() {
- return this._declarationTContainer.tView?.ssrId || null;
- }
- createEmbeddedView(context, injector) {
- return this.createEmbeddedViewImpl(context, injector);
- }
- /**
- * @internal
- */
- createEmbeddedViewImpl(context, injector, hydrationInfo) {
- const embeddedLView = createAndRenderEmbeddedLView(this._declarationLView, this._declarationTContainer, context, { injector, hydrationInfo });
- return new ViewRef(embeddedLView);
- }
- };
- /**
- * Creates a TemplateRef given a node.
- *
- * @returns The TemplateRef instance to use
- */
- function injectTemplateRef() {
- return createTemplateRef(getCurrentTNode(), getLView());
- }
- /**
- * Creates a TemplateRef and stores it on the injector.
- *
- * @param hostTNode The node on which a TemplateRef is requested
- * @param hostLView The `LView` to which the node belongs
- * @returns The TemplateRef instance or null if we can't create a TemplateRef on a given node type
- */
- function createTemplateRef(hostTNode, hostLView) {
- if (hostTNode.type & 4 /* TNodeType.Container */) {
- ngDevMode && assertDefined(hostTNode.tView, 'TView must be allocated');
- return new R3TemplateRef(hostLView, hostTNode, createElementRef(hostTNode, hostLView));
- }
- return null;
- }
- /**
- * Removes all dehydrated views from a given LContainer:
- * both in internal data structure, as well as removing
- * corresponding DOM nodes that belong to that dehydrated view.
- */
- function removeDehydratedViews(lContainer) {
- const views = lContainer[DEHYDRATED_VIEWS] ?? [];
- const parentLView = lContainer[PARENT];
- const renderer = parentLView[RENDERER];
- for (const view of views) {
- removeDehydratedView(view, renderer);
- ngDevMode && ngDevMode.dehydratedViewsRemoved++;
- }
- // Reset the value to an empty array to indicate that no
- // further processing of dehydrated views is needed for
- // this view container (i.e. do not trigger the lookup process
- // once again in case a `ViewContainerRef` is created later).
- lContainer[DEHYDRATED_VIEWS] = EMPTY_ARRAY;
- }
- /**
- * Helper function to remove all nodes from a dehydrated view.
- */
- function removeDehydratedView(dehydratedView, renderer) {
- let nodesRemoved = 0;
- let currentRNode = dehydratedView.firstChild;
- if (currentRNode) {
- const numNodes = dehydratedView.data[NUM_ROOT_NODES];
- while (nodesRemoved < numNodes) {
- ngDevMode && validateSiblingNodeExists(currentRNode);
- const nextSibling = currentRNode.nextSibling;
- nativeRemoveNode(renderer, currentRNode, false);
- currentRNode = nextSibling;
- nodesRemoved++;
- }
- }
- }
- /**
- * Walks over all views within this LContainer invokes dehydrated views
- * cleanup function for each one.
- */
- function cleanupLContainer(lContainer) {
- removeDehydratedViews(lContainer);
- for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
- cleanupLView(lContainer[i]);
- }
- }
- /**
- * Walks over `LContainer`s and components registered within
- * this LView and invokes dehydrated views cleanup function for each one.
- */
- function cleanupLView(lView) {
- const tView = lView[TVIEW];
- for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
- if (isLContainer(lView[i])) {
- const lContainer = lView[i];
- cleanupLContainer(lContainer);
- }
- else if (Array.isArray(lView[i])) {
- // This is a component, enter the `cleanupLView` recursively.
- cleanupLView(lView[i]);
- }
- }
- }
- /**
- * Walks over all views registered within the ApplicationRef and removes
- * all dehydrated views from all `LContainer`s along the way.
- */
- function cleanupDehydratedViews(appRef) {
- const viewRefs = appRef._views;
- for (const viewRef of viewRefs) {
- const lNode = getLNodeForHydration(viewRef);
- // An `lView` might be `null` if a `ViewRef` represents
- // an embedded view (not a component view).
- if (lNode !== null && lNode[HOST] !== null) {
- if (isLView(lNode)) {
- cleanupLView(lNode);
- }
- else {
- // Cleanup in the root component view
- const componentLView = lNode[HOST];
- cleanupLView(componentLView);
- // Cleanup in all views within this view container
- cleanupLContainer(lNode);
- }
- ngDevMode && ngDevMode.dehydratedViewsCleanupRuns++;
- }
- }
- }
- /**
- * Given a current DOM node and a serialized information about the views
- * in a container, walks over the DOM structure, collecting the list of
- * dehydrated views.
- */
- function locateDehydratedViewsInContainer(currentRNode, serializedViews) {
- const dehydratedViews = [];
- for (const serializedView of serializedViews) {
- // Repeats a view multiple times as needed, based on the serialized information
- // (for example, for *ngFor-produced views).
- for (let i = 0; i < (serializedView[MULTIPLIER] ?? 1); i++) {
- const view = {
- data: serializedView,
- firstChild: null,
- };
- if (serializedView[NUM_ROOT_NODES] > 0) {
- // Keep reference to the first node in this view,
- // so it can be accessed while invoking template instructions.
- view.firstChild = currentRNode;
- // Move over to the next node after this view, which can
- // either be a first node of the next view or an anchor comment
- // node after the last view in a container.
- currentRNode = siblingAfter(serializedView[NUM_ROOT_NODES], currentRNode);
- }
- dehydratedViews.push(view);
- }
- }
- return [currentRNode, dehydratedViews];
- }
- /**
- * Reference to a function that searches for a matching dehydrated views
- * stored on a given lContainer.
- * Returns `null` by default, when hydration is not enabled.
- */
- let _findMatchingDehydratedViewImpl = (lContainer, template) => null;
- /**
- * Retrieves the next dehydrated view from the LContainer and verifies that
- * it matches a given template id (from the TView that was used to create this
- * instance of a view). If the id doesn't match, that means that we are in an
- * unexpected state and can not complete the reconciliation process. Thus,
- * all dehydrated views from this LContainer are removed (including corresponding
- * DOM nodes) and the rendering is performed as if there were no dehydrated views
- * in this container.
- */
- function findMatchingDehydratedViewImpl(lContainer, template) {
- const views = lContainer[DEHYDRATED_VIEWS];
- if (!template || views === null || views.length === 0) {
- return null;
- }
- const view = views[0];
- // Verify whether the first dehydrated view in the container matches
- // the template id passed to this function (that originated from a TView
- // that was used to create an instance of an embedded or component views.
- if (view.data[TEMPLATE_ID] === template) {
- // If the template id matches - extract the first view and return it.
- return views.shift();
- }
- else {
- // Otherwise, we are at the state when reconciliation can not be completed,
- // thus we remove all dehydrated views within this container (remove them
- // from internal data structures as well as delete associated elements from
- // the DOM tree).
- removeDehydratedViews(lContainer);
- return null;
- }
- }
- function enableFindMatchingDehydratedViewImpl() {
- _findMatchingDehydratedViewImpl = findMatchingDehydratedViewImpl;
- }
- function findMatchingDehydratedView(lContainer, template) {
- return _findMatchingDehydratedViewImpl(lContainer, template);
- }
- /**
- * Represents a container where one or more views can be attached to a component.
- *
- * Can contain *host views* (created by instantiating a
- * component with the `createComponent()` method), and *embedded views*
- * (created by instantiating a `TemplateRef` with the `createEmbeddedView()` method).
- *
- * A view container instance can contain other view containers,
- * creating a [view hierarchy](guide/glossary#view-hierarchy).
- *
- * @usageNotes
- *
- * The example below demonstrates how the `createComponent` function can be used
- * to create an instance of a ComponentRef dynamically and attach it to an ApplicationRef,
- * so that it gets included into change detection cycles.
- *
- * Note: the example uses standalone components, but the function can also be used for
- * non-standalone components (declared in an NgModule) as well.
- *
- * ```typescript
- * @Component({
- * standalone: true,
- * selector: 'dynamic',
- * template: `<span>This is a content of a dynamic component.</span>`,
- * })
- * class DynamicComponent {
- * vcr = inject(ViewContainerRef);
- * }
- *
- * @Component({
- * standalone: true,
- * selector: 'app',
- * template: `<main>Hi! This is the main content.</main>`,
- * })
- * class AppComponent {
- * vcr = inject(ViewContainerRef);
- *
- * ngAfterViewInit() {
- * const compRef = this.vcr.createComponent(DynamicComponent);
- * compRef.changeDetectorRef.detectChanges();
- * }
- * }
- * ```
- *
- * @see {@link ComponentRef}
- * @see {@link EmbeddedViewRef}
- *
- * @publicApi
- */
- class ViewContainerRef {
- /**
- * @internal
- * @nocollapse
- */
- static { this.__NG_ELEMENT_ID__ = injectViewContainerRef; }
- }
- /**
- * Creates a ViewContainerRef and stores it on the injector. Or, if the ViewContainerRef
- * already exists, retrieves the existing ViewContainerRef.
- *
- * @returns The ViewContainerRef instance to use
- */
- function injectViewContainerRef() {
- const previousTNode = getCurrentTNode();
- return createContainerRef(previousTNode, getLView());
- }
- const VE_ViewContainerRef = ViewContainerRef;
- // TODO(alxhub): cleaning up this indirection triggers a subtle bug in Closure in g3. Once the fix
- // for that lands, this can be cleaned up.
- const R3ViewContainerRef = class ViewContainerRef extends VE_ViewContainerRef {
- constructor(_lContainer, _hostTNode, _hostLView) {
- super();
- this._lContainer = _lContainer;
- this._hostTNode = _hostTNode;
- this._hostLView = _hostLView;
- }
- get element() {
- return createElementRef(this._hostTNode, this._hostLView);
- }
- get injector() {
- return new NodeInjector(this._hostTNode, this._hostLView);
- }
- /** @deprecated No replacement */
- get parentInjector() {
- const parentLocation = getParentInjectorLocation(this._hostTNode, this._hostLView);
- if (hasParentInjector(parentLocation)) {
- const parentView = getParentInjectorView(parentLocation, this._hostLView);
- const injectorIndex = getParentInjectorIndex(parentLocation);
- ngDevMode && assertNodeInjector(parentView, injectorIndex);
- const parentTNode = parentView[TVIEW].data[injectorIndex + 8 /* NodeInjectorOffset.TNODE */];
- return new NodeInjector(parentTNode, parentView);
- }
- else {
- return new NodeInjector(null, this._hostLView);
- }
- }
- clear() {
- while (this.length > 0) {
- this.remove(this.length - 1);
- }
- }
- get(index) {
- const viewRefs = getViewRefs(this._lContainer);
- return viewRefs !== null && viewRefs[index] || null;
- }
- get length() {
- return this._lContainer.length - CONTAINER_HEADER_OFFSET;
- }
- createEmbeddedView(templateRef, context, indexOrOptions) {
- let index;
- let injector;
- if (typeof indexOrOptions === 'number') {
- index = indexOrOptions;
- }
- else if (indexOrOptions != null) {
- index = indexOrOptions.index;
- injector = indexOrOptions.injector;
- }
- const hydrationInfo = findMatchingDehydratedView(this._lContainer, templateRef.ssrId);
- const viewRef = templateRef.createEmbeddedViewImpl(context || {}, injector, hydrationInfo);
- // If there is a matching dehydrated view, but the host TNode is located in the skip
- // hydration block, this means that the content was detached (as a part of the skip
- // hydration logic) and it needs to be appended into the DOM.
- const skipDomInsertion = !!hydrationInfo && !hasInSkipHydrationBlockFlag(this._hostTNode);
- this.insertImpl(viewRef, index, skipDomInsertion);
- return viewRef;
- }
- createComponent(componentFactoryOrType, indexOrOptions, injector, projectableNodes, environmentInjector) {
- const isComponentFactory = componentFactoryOrType && !isType(componentFactoryOrType);
- let index;
- // This function supports 2 signatures and we need to handle options correctly for both:
- // 1. When first argument is a Component type. This signature also requires extra
- // options to be provided as object (more ergonomic option).
- // 2. First argument is a Component factory. In this case extra options are represented as
- // positional arguments. This signature is less ergonomic and will be deprecated.
- if (isComponentFactory) {
- if (ngDevMode) {
- assertEqual(typeof indexOrOptions !== 'object', true, 'It looks like Component factory was provided as the first argument ' +
- 'and an options object as the second argument. This combination of arguments ' +
- 'is incompatible. You can either change the first argument to provide Component ' +
- 'type or change the second argument to be a number (representing an index at ' +
- 'which to insert the new component\'s host view into this container)');
- }
- index = indexOrOptions;
- }
- else {
- if (ngDevMode) {
- assertDefined(getComponentDef$1(componentFactoryOrType), `Provided Component class doesn't contain Component definition. ` +
- `Please check whether provided class has @Component decorator.`);
- assertEqual(typeof indexOrOptions !== 'number', true, 'It looks like Component type was provided as the first argument ' +
- 'and a number (representing an index at which to insert the new component\'s ' +
- 'host view into this container as the second argument. This combination of arguments ' +
- 'is incompatible. Please use an object as the second argument instead.');
- }
- const options = (indexOrOptions || {});
- if (ngDevMode && options.environmentInjector && options.ngModuleRef) {
- throwError(`Cannot pass both environmentInjector and ngModuleRef options to createComponent().`);
- }
- index = options.index;
- injector = options.injector;
- projectableNodes = options.projectableNodes;
- environmentInjector = options.environmentInjector || options.ngModuleRef;
- }
- const componentFactory = isComponentFactory ?
- componentFactoryOrType :
- new ComponentFactory(getComponentDef$1(componentFactoryOrType));
- const contextInjector = injector || this.parentInjector;
- // If an `NgModuleRef` is not provided explicitly, try retrieving it from the DI tree.
- if (!environmentInjector && componentFactory.ngModule == null) {
- // For the `ComponentFactory` case, entering this logic is very unlikely, since we expect that
- // an instance of a `ComponentFactory`, resolved via `ComponentFactoryResolver` would have an
- // `ngModule` field. This is possible in some test scenarios and potentially in some JIT-based
- // use-cases. For the `ComponentFactory` case we preserve backwards-compatibility and try
- // using a provided injector first, then fall back to the parent injector of this
- // `ViewContainerRef` instance.
- //
- // For the factory-less case, it's critical to establish a connection with the module
- // injector tree (by retrieving an instance of an `NgModuleRef` and accessing its injector),
- // so that a component can use DI tokens provided in MgModules. For this reason, we can not
- // rely on the provided injector, since it might be detached from the DI tree (for example, if
- // it was created via `Injector.create` without specifying a parent injector, or if an
- // injector is retrieved from an `NgModuleRef` created via `createNgModule` using an
- // NgModule outside of a module tree). Instead, we always use `ViewContainerRef`'s parent
- // injector, which is normally connected to the DI tree, which includes module injector
- // subtree.
- const _injector = isComponentFactory ? contextInjector : this.parentInjector;
- // DO NOT REFACTOR. The code here used to have a `injector.get(NgModuleRef, null) ||
- // undefined` expression which seems to cause internal google apps to fail. This is documented
- // in the following internal bug issue: go/b/142967802
- const result = _injector.get(EnvironmentInjector, null);
- if (result) {
- environmentInjector = result;
- }
- }
- const componentDef = getComponentDef$1(componentFactory.componentType ?? {});
- const dehydratedView = findMatchingDehydratedView(this._lContainer, componentDef?.id ?? null);
- const rNode = dehydratedView?.firstChild ?? null;
- const componentRef = componentFactory.create(contextInjector, projectableNodes, rNode, environmentInjector);
- // If there is a matching dehydrated view, but the host TNode is located in the skip
- // hydration block, this means that the content was detached (as a part of the skip
- // hydration logic) and it needs to be appended into the DOM.
- const skipDomInsertion = !!dehydratedView && !hasInSkipHydrationBlockFlag(this._hostTNode);
- this.insertImpl(componentRef.hostView, index, skipDomInsertion);
- return componentRef;
- }
- insert(viewRef, index) {
- return this.insertImpl(viewRef, index, false);
- }
- insertImpl(viewRef, index, skipDomInsertion) {
- const lView = viewRef._lView;
- const tView = lView[TVIEW];
- if (ngDevMode && viewRef.destroyed) {
- throw new Error('Cannot insert a destroyed View in a ViewContainer!');
- }
- if (viewAttachedToContainer(lView)) {
- // If view is already attached, detach it first so we clean up references appropriately.
- const prevIdx = this.indexOf(viewRef);
- // A view might be attached either to this or a different container. The `prevIdx` for
- // those cases will be:
- // equal to -1 for views attached to this ViewContainerRef
- // >= 0 for views attached to a different ViewContainerRef
- if (prevIdx !== -1) {
- this.detach(prevIdx);
- }
- else {
- const prevLContainer = lView[PARENT];
- ngDevMode &&
- assertEqual(isLContainer(prevLContainer), true, 'An attached view should have its PARENT point to a container.');
- // We need to re-create a R3ViewContainerRef instance since those are not stored on
- // LView (nor anywhere else).
- const prevVCRef = new R3ViewContainerRef(prevLContainer, prevLContainer[T_HOST], prevLContainer[PARENT]);
- prevVCRef.detach(prevVCRef.indexOf(viewRef));
- }
- }
- // Logical operation of adding `LView` to `LContainer`
- const adjustedIdx = this._adjustIndex(index);
- const lContainer = this._lContainer;
- addLViewToLContainer(lContainer, lView, adjustedIdx, !skipDomInsertion);
- viewRef.attachToViewContainerRef();
- addToArray(getOrCreateViewRefs(lContainer), adjustedIdx, viewRef);
- return viewRef;
- }
- move(viewRef, newIndex) {
- if (ngDevMode && viewRef.destroyed) {
- throw new Error('Cannot move a destroyed View in a ViewContainer!');
- }
- return this.insert(viewRef, newIndex);
- }
- indexOf(viewRef) {
- const viewRefsArr = getViewRefs(this._lContainer);
- return viewRefsArr !== null ? viewRefsArr.indexOf(viewRef) : -1;
- }
- remove(index) {
- const adjustedIdx = this._adjustIndex(index, -1);
- const detachedView = detachView(this._lContainer, adjustedIdx);
- if (detachedView) {
- // Before destroying the view, remove it from the container's array of `ViewRef`s.
- // This ensures the view container length is updated before calling
- // `destroyLView`, which could recursively call view container methods that
- // rely on an accurate container length.
- // (e.g. a method on this view container being called by a child directive's OnDestroy
- // lifecycle hook)
- removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx);
- destroyLView(detachedView[TVIEW], detachedView);
- }
- }
- detach(index) {
- const adjustedIdx = this._adjustIndex(index, -1);
- const view = detachView(this._lContainer, adjustedIdx);
- const wasDetached = view && removeFromArray(getOrCreateViewRefs(this._lContainer), adjustedIdx) != null;
- return wasDetached ? new ViewRef(view) : null;
- }
- _adjustIndex(index, shift = 0) {
- if (index == null) {
- return this.length + shift;
- }
- if (ngDevMode) {
- assertGreaterThan(index, -1, `ViewRef index must be positive, got ${index}`);
- // +1 because it's legal to insert at the end.
- assertLessThan(index, this.length + 1 + shift, 'index');
- }
- return index;
- }
- };
- function getViewRefs(lContainer) {
- return lContainer[VIEW_REFS];
- }
- function getOrCreateViewRefs(lContainer) {
- return (lContainer[VIEW_REFS] || (lContainer[VIEW_REFS] = []));
- }
- /**
- * Creates a ViewContainerRef and stores it on the injector.
- *
- * @param hostTNode The node that is requesting a ViewContainerRef
- * @param hostLView The view to which the node belongs
- * @returns The ViewContainerRef instance to use
- */
- function createContainerRef(hostTNode, hostLView) {
- ngDevMode && assertTNodeType(hostTNode, 12 /* TNodeType.AnyContainer */ | 3 /* TNodeType.AnyRNode */);
- let lContainer;
- const slotValue = hostLView[hostTNode.index];
- if (isLContainer(slotValue)) {
- // If the host is a container, we don't need to create a new LContainer
- lContainer = slotValue;
- }
- else {
- // An LContainer anchor can not be `null`, but we set it here temporarily
- // and update to the actual value later in this function (see
- // `_locateOrCreateAnchorNode`).
- lContainer = createLContainer(slotValue, hostLView, null, hostTNode);
- hostLView[hostTNode.index] = lContainer;
- addToViewTree(hostLView, lContainer);
- }
- _locateOrCreateAnchorNode(lContainer, hostLView, hostTNode, slotValue);
- return new R3ViewContainerRef(lContainer, hostTNode, hostLView);
- }
- /**
- * Creates and inserts a comment node that acts as an anchor for a view container.
- *
- * If the host is a regular element, we have to insert a comment node manually which will
- * be used as an anchor when inserting elements. In this specific case we use low-level DOM
- * manipulation to insert it.
- */
- function insertAnchorNode(hostLView, hostTNode) {
- const renderer = hostLView[RENDERER];
- ngDevMode && ngDevMode.rendererCreateComment++;
- const commentNode = renderer.createComment(ngDevMode ? 'container' : '');
- const hostNative = getNativeByTNode(hostTNode, hostLView);
- const parentOfHostNative = nativeParentNode(renderer, hostNative);
- nativeInsertBefore(renderer, parentOfHostNative, commentNode, nativeNextSibling(renderer, hostNative), false);
- return commentNode;
- }
- let _locateOrCreateAnchorNode = createAnchorNode;
- /**
- * Regular creation mode: an anchor is created and
- * assigned to the `lContainer[NATIVE]` slot.
- */
- function createAnchorNode(lContainer, hostLView, hostTNode, slotValue) {
- // We already have a native element (anchor) set, return.
- if (lContainer[NATIVE])
- return;
- let commentNode;
- // If the host is an element container, the native host element is guaranteed to be a
- // comment and we can reuse that comment as anchor element for the new LContainer.
- // The comment node in question is already part of the DOM structure so we don't need to append
- // it again.
- if (hostTNode.type & 8 /* TNodeType.ElementContainer */) {
- commentNode = unwrapRNode(slotValue);
- }
- else {
- commentNode = insertAnchorNode(hostLView, hostTNode);
- }
- lContainer[NATIVE] = commentNode;
- }
- /**
- * Hydration logic that looks up:
- * - an anchor node in the DOM and stores the node in `lContainer[NATIVE]`
- * - all dehydrated views in this container and puts them into `lContainer[DEHYDRATED_VIEWS]`
- */
- function locateOrCreateAnchorNode(lContainer, hostLView, hostTNode, slotValue) {
- // We already have a native element (anchor) set and the process
- // of finding dehydrated views happened (so the `lContainer[DEHYDRATED_VIEWS]`
- // is not null), exit early.
- if (lContainer[NATIVE] && lContainer[DEHYDRATED_VIEWS])
- return;
- const hydrationInfo = hostLView[HYDRATION];
- const noOffsetIndex = hostTNode.index - HEADER_OFFSET;
- // TODO(akushnir): this should really be a single condition, refactor the code
- // to use `hasInSkipHydrationBlockFlag` logic inside `isInSkipHydrationBlock`.
- const skipHydration = isInSkipHydrationBlock(hostTNode) || hasInSkipHydrationBlockFlag(hostTNode);
- const isNodeCreationMode = !hydrationInfo || skipHydration || isDisconnectedNode(hydrationInfo, noOffsetIndex);
- // Regular creation mode.
- if (isNodeCreationMode) {
- return createAnchorNode(lContainer, hostLView, hostTNode, slotValue);
- }
- // Hydration mode, looking up an anchor node and dehydrated views in DOM.
- const currentRNode = getSegmentHead(hydrationInfo, noOffsetIndex);
- const serializedViews = hydrationInfo.data[CONTAINERS]?.[noOffsetIndex];
- ngDevMode &&
- assertDefined(serializedViews, 'Unexpected state: no hydration info available for a given TNode, ' +
- 'which represents a view container.');
- const [commentNode, dehydratedViews] = locateDehydratedViewsInContainer(currentRNode, serializedViews);
- if (ngDevMode) {
- validateMatchingNode(commentNode, Node.COMMENT_NODE, null, hostLView, hostTNode, true);
- // Do not throw in case this node is already claimed (thus `false` as a second
- // argument). If this container is created based on an `<ng-template>`, the comment
- // node would be already claimed from the `template` instruction. If an element acts
- // as an anchor (e.g. <div #vcRef>), a separate comment node would be created/located,
- // so we need to claim it here.
- markRNodeAsClaimedByHydration(commentNode, false);
- }
- lContainer[NATIVE] = commentNode;
- lContainer[DEHYDRATED_VIEWS] = dehydratedViews;
- }
- function enableLocateOrCreateContainerRefImpl() {
- _locateOrCreateAnchorNode = locateOrCreateAnchorNode;
- }
- class LQuery_ {
- constructor(queryList) {
- this.queryList = queryList;
- this.matches = null;
- }
- clone() {
- return new LQuery_(this.queryList);
- }
- setDirty() {
- this.queryList.setDirty();
- }
- }
- class LQueries_ {
- constructor(queries = []) {
- this.queries = queries;
- }
- createEmbeddedView(tView) {
- const tQueries = tView.queries;
- if (tQueries !== null) {
- const noOfInheritedQueries = tView.contentQueries !== null ? tView.contentQueries[0] : tQueries.length;
- const viewLQueries = [];
- // An embedded view has queries propagated from a declaration view at the beginning of the
- // TQueries collection and up until a first content query declared in the embedded view. Only
- // propagated LQueries are created at this point (LQuery corresponding to declared content
- // queries will be instantiated from the content query instructions for each directive).
- for (let i = 0; i < noOfInheritedQueries; i++) {
- const tQuery = tQueries.getByIndex(i);
- const parentLQuery = this.queries[tQuery.indexInDeclarationView];
- viewLQueries.push(parentLQuery.clone());
- }
- return new LQueries_(viewLQueries);
- }
- return null;
- }
- insertView(tView) {
- this.dirtyQueriesWithMatches(tView);
- }
- detachView(tView) {
- this.dirtyQueriesWithMatches(tView);
- }
- dirtyQueriesWithMatches(tView) {
- for (let i = 0; i < this.queries.length; i++) {
- if (getTQuery(tView, i).matches !== null) {
- this.queries[i].setDirty();
- }
- }
- }
- }
- class TQueryMetadata_ {
- constructor(predicate, flags, read = null) {
- this.predicate = predicate;
- this.flags = flags;
- this.read = read;
- }
- }
- class TQueries_ {
- constructor(queries = []) {
- this.queries = queries;
- }
- elementStart(tView, tNode) {
- ngDevMode &&
- assertFirstCreatePass(tView, 'Queries should collect results on the first template pass only');
- for (let i = 0; i < this.queries.length; i++) {
- this.queries[i].elementStart(tView, tNode);
- }
- }
- elementEnd(tNode) {
- for (let i = 0; i < this.queries.length; i++) {
- this.queries[i].elementEnd(tNode);
- }
- }
- embeddedTView(tNode) {
- let queriesForTemplateRef = null;
- for (let i = 0; i < this.length; i++) {
- const childQueryIndex = queriesForTemplateRef !== null ? queriesForTemplateRef.length : 0;
- const tqueryClone = this.getByIndex(i).embeddedTView(tNode, childQueryIndex);
- if (tqueryClone) {
- tqueryClone.indexInDeclarationView = i;
- if (queriesForTemplateRef !== null) {
- queriesForTemplateRef.push(tqueryClone);
- }
- else {
- queriesForTemplateRef = [tqueryClone];
- }
- }
- }
- return queriesForTemplateRef !== null ? new TQueries_(queriesForTemplateRef) : null;
- }
- template(tView, tNode) {
- ngDevMode &&
- assertFirstCreatePass(tView, 'Queries should collect results on the first template pass only');
- for (let i = 0; i < this.queries.length; i++) {
- this.queries[i].template(tView, tNode);
- }
- }
- getByIndex(index) {
- ngDevMode && assertIndexInRange(this.queries, index);
- return this.queries[index];
- }
- get length() {
- return this.queries.length;
- }
- track(tquery) {
- this.queries.push(tquery);
- }
- }
- class TQuery_ {
- constructor(metadata, nodeIndex = -1) {
- this.metadata = metadata;
- this.matches = null;
- this.indexInDeclarationView = -1;
- this.crossesNgTemplate = false;
- /**
- * A flag indicating if a given query still applies to nodes it is crossing. We use this flag
- * (alongside with _declarationNodeIndex) to know when to stop applying content queries to
- * elements in a template.
- */
- this._appliesToNextNode = true;
- this._declarationNodeIndex = nodeIndex;
- }
- elementStart(tView, tNode) {
- if (this.isApplyingToNode(tNode)) {
- this.matchTNode(tView, tNode);
- }
- }
- elementEnd(tNode) {
- if (this._declarationNodeIndex === tNode.index) {
- this._appliesToNextNode = false;
- }
- }
- template(tView, tNode) {
- this.elementStart(tView, tNode);
- }
- embeddedTView(tNode, childQueryIndex) {
- if (this.isApplyingToNode(tNode)) {
- this.crossesNgTemplate = true;
- // A marker indicating a `<ng-template>` element (a placeholder for query results from
- // embedded views created based on this `<ng-template>`).
- this.addMatch(-tNode.index, childQueryIndex);
- return new TQuery_(this.metadata);
- }
- return null;
- }
- isApplyingToNode(tNode) {
- if (this._appliesToNextNode &&
- (this.metadata.flags & 1 /* QueryFlags.descendants */) !== 1 /* QueryFlags.descendants */) {
- const declarationNodeIdx = this._declarationNodeIndex;
- let parent = tNode.parent;
- // Determine if a given TNode is a "direct" child of a node on which a content query was
- // declared (only direct children of query's host node can match with the descendants: false
- // option). There are 3 main use-case / conditions to consider here:
- // - <needs-target><i #target></i></needs-target>: here <i #target> parent node is a query
- // host node;
- // - <needs-target><ng-template [ngIf]="true"><i #target></i></ng-template></needs-target>:
- // here <i #target> parent node is null;
- // - <needs-target><ng-container><i #target></i></ng-container></needs-target>: here we need
- // to go past `<ng-container>` to determine <i #target> parent node (but we shouldn't traverse
- // up past the query's host node!).
- while (parent !== null && (parent.type & 8 /* TNodeType.ElementContainer */) &&
- parent.index !== declarationNodeIdx) {
- parent = parent.parent;
- }
- return declarationNodeIdx === (parent !== null ? parent.index : -1);
- }
- return this._appliesToNextNode;
- }
- matchTNode(tView, tNode) {
- const predicate = this.metadata.predicate;
- if (Array.isArray(predicate)) {
- for (let i = 0; i < predicate.length; i++) {
- const name = predicate[i];
- this.matchTNodeWithReadOption(tView, tNode, getIdxOfMatchingSelector(tNode, name));
- // Also try matching the name to a provider since strings can be used as DI tokens too.
- this.matchTNodeWithReadOption(tView, tNode, locateDirectiveOrProvider(tNode, tView, name, false, false));
- }
- }
- else {
- if (predicate === TemplateRef) {
- if (tNode.type & 4 /* TNodeType.Container */) {
- this.matchTNodeWithReadOption(tView, tNode, -1);
- }
- }
- else {
- this.matchTNodeWithReadOption(tView, tNode, locateDirectiveOrProvider(tNode, tView, predicate, false, false));
- }
- }
- }
- matchTNodeWithReadOption(tView, tNode, nodeMatchIdx) {
- if (nodeMatchIdx !== null) {
- const read = this.metadata.read;
- if (read !== null) {
- if (read === ElementRef || read === ViewContainerRef ||
- read === TemplateRef && (tNode.type & 4 /* TNodeType.Container */)) {
- this.addMatch(tNode.index, -2);
- }
- else {
- const directiveOrProviderIdx = locateDirectiveOrProvider(tNode, tView, read, false, false);
- if (directiveOrProviderIdx !== null) {
- this.addMatch(tNode.index, directiveOrProviderIdx);
- }
- }
- }
- else {
- this.addMatch(tNode.index, nodeMatchIdx);
- }
- }
- }
- addMatch(tNodeIdx, matchIdx) {
- if (this.matches === null) {
- this.matches = [tNodeIdx, matchIdx];
- }
- else {
- this.matches.push(tNodeIdx, matchIdx);
- }
- }
- }
- /**
- * Iterates over local names for a given node and returns directive index
- * (or -1 if a local name points to an element).
- *
- * @param tNode static data of a node to check
- * @param selector selector to match
- * @returns directive index, -1 or null if a selector didn't match any of the local names
- */
- function getIdxOfMatchingSelector(tNode, selector) {
- const localNames = tNode.localNames;
- if (localNames !== null) {
- for (let i = 0; i < localNames.length; i += 2) {
- if (localNames[i] === selector) {
- return localNames[i + 1];
- }
- }
- }
- return null;
- }
- function createResultByTNodeType(tNode, currentView) {
- if (tNode.type & (3 /* TNodeType.AnyRNode */ | 8 /* TNodeType.ElementContainer */)) {
- return createElementRef(tNode, currentView);
- }
- else if (tNode.type & 4 /* TNodeType.Container */) {
- return createTemplateRef(tNode, currentView);
- }
- return null;
- }
- function createResultForNode(lView, tNode, matchingIdx, read) {
- if (matchingIdx === -1) {
- // if read token and / or strategy is not specified, detect it using appropriate tNode type
- return createResultByTNodeType(tNode, lView);
- }
- else if (matchingIdx === -2) {
- // read a special token from a node injector
- return createSpecialToken(lView, tNode, read);
- }
- else {
- // read a token
- return getNodeInjectable(lView, lView[TVIEW], matchingIdx, tNode);
- }
- }
- function createSpecialToken(lView, tNode, read) {
- if (read === ElementRef) {
- return createElementRef(tNode, lView);
- }
- else if (read === TemplateRef) {
- return createTemplateRef(tNode, lView);
- }
- else if (read === ViewContainerRef) {
- ngDevMode && assertTNodeType(tNode, 3 /* TNodeType.AnyRNode */ | 12 /* TNodeType.AnyContainer */);
- return createContainerRef(tNode, lView);
- }
- else {
- ngDevMode &&
- throwError(`Special token to read should be one of ElementRef, TemplateRef or ViewContainerRef but got ${stringify(read)}.`);
- }
- }
- /**
- * A helper function that creates query results for a given view. This function is meant to do the
- * processing once and only once for a given view instance (a set of results for a given view
- * doesn't change).
- */
- function materializeViewResults(tView, lView, tQuery, queryIndex) {
- const lQuery = lView[QUERIES].queries[queryIndex];
- if (lQuery.matches === null) {
- const tViewData = tView.data;
- const tQueryMatches = tQuery.matches;
- const result = [];
- for (let i = 0; i < tQueryMatches.length; i += 2) {
- const matchedNodeIdx = tQueryMatches[i];
- if (matchedNodeIdx < 0) {
- // we at the <ng-template> marker which might have results in views created based on this
- // <ng-template> - those results will be in separate views though, so here we just leave
- // null as a placeholder
- result.push(null);
- }
- else {
- ngDevMode && assertIndexInRange(tViewData, matchedNodeIdx);
- const tNode = tViewData[matchedNodeIdx];
- result.push(createResultForNode(lView, tNode, tQueryMatches[i + 1], tQuery.metadata.read));
- }
- }
- lQuery.matches = result;
- }
- return lQuery.matches;
- }
- /**
- * A helper function that collects (already materialized) query results from a tree of views,
- * starting with a provided LView.
- */
- function collectQueryResults(tView, lView, queryIndex, result) {
- const tQuery = tView.queries.getByIndex(queryIndex);
- const tQueryMatches = tQuery.matches;
- if (tQueryMatches !== null) {
- const lViewResults = materializeViewResults(tView, lView, tQuery, queryIndex);
- for (let i = 0; i < tQueryMatches.length; i += 2) {
- const tNodeIdx = tQueryMatches[i];
- if (tNodeIdx > 0) {
- result.push(lViewResults[i / 2]);
- }
- else {
- const childQueryIndex = tQueryMatches[i + 1];
- const declarationLContainer = lView[-tNodeIdx];
- ngDevMode && assertLContainer(declarationLContainer);
- // collect matches for views inserted in this container
- for (let i = CONTAINER_HEADER_OFFSET; i < declarationLContainer.length; i++) {
- const embeddedLView = declarationLContainer[i];
- if (embeddedLView[DECLARATION_LCONTAINER] === embeddedLView[PARENT]) {
- collectQueryResults(embeddedLView[TVIEW], embeddedLView, childQueryIndex, result);
- }
- }
- // collect matches for views created from this declaration container and inserted into
- // different containers
- if (declarationLContainer[MOVED_VIEWS] !== null) {
- const embeddedLViews = declarationLContainer[MOVED_VIEWS];
- for (let i = 0; i < embeddedLViews.length; i++) {
- const embeddedLView = embeddedLViews[i];
- collectQueryResults(embeddedLView[TVIEW], embeddedLView, childQueryIndex, result);
- }
- }
- }
- }
- }
- return result;
- }
- /**
- * Refreshes a query by combining matches from all active views and removing matches from deleted
- * views.
- *
- * @returns `true` if a query got dirty during change detection or if this is a static query
- * resolving in creation mode, `false` otherwise.
- *
- * @codeGenApi
- */
- function ɵɵqueryRefresh(queryList) {
- const lView = getLView();
- const tView = getTView();
- const queryIndex = getCurrentQueryIndex();
- setCurrentQueryIndex(queryIndex + 1);
- const tQuery = getTQuery(tView, queryIndex);
- if (queryList.dirty &&
- (isCreationMode(lView) ===
- ((tQuery.metadata.flags & 2 /* QueryFlags.isStatic */) === 2 /* QueryFlags.isStatic */))) {
- if (tQuery.matches === null) {
- queryList.reset([]);
- }
- else {
- const result = tQuery.crossesNgTemplate ?
- collectQueryResults(tView, lView, queryIndex, []) :
- materializeViewResults(tView, lView, tQuery, queryIndex);
- queryList.reset(result, unwrapElementRef);
- queryList.notifyOnChanges();
- }
- return true;
- }
- return false;
- }
- /**
- * Creates new QueryList, stores the reference in LView and returns QueryList.
- *
- * @param predicate The type for which the query will search
- * @param flags Flags associated with the query
- * @param read What to save in the query
- *
- * @codeGenApi
- */
- function ɵɵviewQuery(predicate, flags, read) {
- ngDevMode && assertNumber(flags, 'Expecting flags');
- const tView = getTView();
- if (tView.firstCreatePass) {
- createTQuery(tView, new TQueryMetadata_(predicate, flags, read), -1);
- if ((flags & 2 /* QueryFlags.isStatic */) === 2 /* QueryFlags.isStatic */) {
- tView.staticViewQueries = true;
- }
- }
- createLQuery(tView, getLView(), flags);
- }
- /**
- * Registers a QueryList, associated with a content query, for later refresh (part of a view
- * refresh).
- *
- * @param directiveIndex Current directive index
- * @param predicate The type for which the query will search
- * @param flags Flags associated with the query
- * @param read What to save in the query
- * @returns QueryList<T>
- *
- * @codeGenApi
- */
- function ɵɵcontentQuery(directiveIndex, predicate, flags, read) {
- ngDevMode && assertNumber(flags, 'Expecting flags');
- const tView = getTView();
- if (tView.firstCreatePass) {
- const tNode = getCurrentTNode();
- createTQuery(tView, new TQueryMetadata_(predicate, flags, read), tNode.index);
- saveContentQueryAndDirectiveIndex(tView, directiveIndex);
- if ((flags & 2 /* QueryFlags.isStatic */) === 2 /* QueryFlags.isStatic */) {
- tView.staticContentQueries = true;
- }
- }
- createLQuery(tView, getLView(), flags);
- }
- /**
- * Loads a QueryList corresponding to the current view or content query.
- *
- * @codeGenApi
- */
- function ɵɵloadQuery() {
- return loadQueryInternal(getLView(), getCurrentQueryIndex());
- }
- function loadQueryInternal(lView, queryIndex) {
- ngDevMode &&
- assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
- ngDevMode && assertIndexInRange(lView[QUERIES].queries, queryIndex);
- return lView[QUERIES].queries[queryIndex].queryList;
- }
- function createLQuery(tView, lView, flags) {
- const queryList = new QueryList((flags & 4 /* QueryFlags.emitDistinctChangesOnly */) === 4 /* QueryFlags.emitDistinctChangesOnly */);
- storeCleanupWithContext(tView, lView, queryList, queryList.destroy);
- if (lView[QUERIES] === null)
- lView[QUERIES] = new LQueries_();
- lView[QUERIES].queries.push(new LQuery_(queryList));
- }
- function createTQuery(tView, metadata, nodeIndex) {
- if (tView.queries === null)
- tView.queries = new TQueries_();
- tView.queries.track(new TQuery_(metadata, nodeIndex));
- }
- function saveContentQueryAndDirectiveIndex(tView, directiveIndex) {
- const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
- const lastSavedDirectiveIndex = tViewContentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1;
- if (directiveIndex !== lastSavedDirectiveIndex) {
- tViewContentQueries.push(tView.queries.length - 1, directiveIndex);
- }
- }
- function getTQuery(tView, index) {
- ngDevMode && assertDefined(tView.queries, 'TQueries must be defined to retrieve a TQuery');
- return tView.queries.getByIndex(index);
- }
- /**
- * Retrieves `TemplateRef` instance from `Injector` when a local reference is placed on the
- * `<ng-template>` element.
- *
- * @codeGenApi
- */
- function ɵɵtemplateRefExtractor(tNode, lView) {
- return createTemplateRef(tNode, lView);
- }
- /**
- * A mapping of the @angular/core API surface used in generated expressions to the actual symbols.
- *
- * This should be kept up to date with the public exports of @angular/core.
- */
- const angularCoreEnv = (() => ({
- 'ɵɵattribute': ɵɵattribute,
- 'ɵɵattributeInterpolate1': ɵɵattributeInterpolate1,
- 'ɵɵattributeInterpolate2': ɵɵattributeInterpolate2,
- 'ɵɵattributeInterpolate3': ɵɵattributeInterpolate3,
- 'ɵɵattributeInterpolate4': ɵɵattributeInterpolate4,
- 'ɵɵattributeInterpolate5': ɵɵattributeInterpolate5,
- 'ɵɵattributeInterpolate6': ɵɵattributeInterpolate6,
- 'ɵɵattributeInterpolate7': ɵɵattributeInterpolate7,
- 'ɵɵattributeInterpolate8': ɵɵattributeInterpolate8,
- 'ɵɵattributeInterpolateV': ɵɵattributeInterpolateV,
- 'ɵɵdefineComponent': ɵɵdefineComponent,
- 'ɵɵdefineDirective': ɵɵdefineDirective,
- 'ɵɵdefineInjectable': ɵɵdefineInjectable,
- 'ɵɵdefineInjector': ɵɵdefineInjector,
- 'ɵɵdefineNgModule': ɵɵdefineNgModule,
- 'ɵɵdefinePipe': ɵɵdefinePipe,
- 'ɵɵdirectiveInject': ɵɵdirectiveInject,
- 'ɵɵgetInheritedFactory': ɵɵgetInheritedFactory,
- 'ɵɵinject': ɵɵinject,
- 'ɵɵinjectAttribute': ɵɵinjectAttribute,
- 'ɵɵinvalidFactory': ɵɵinvalidFactory,
- 'ɵɵinvalidFactoryDep': ɵɵinvalidFactoryDep,
- 'ɵɵtemplateRefExtractor': ɵɵtemplateRefExtractor,
- 'ɵɵresetView': ɵɵresetView,
- 'ɵɵHostDirectivesFeature': ɵɵHostDirectivesFeature,
- 'ɵɵNgOnChangesFeature': ɵɵNgOnChangesFeature,
- 'ɵɵProvidersFeature': ɵɵProvidersFeature,
- 'ɵɵCopyDefinitionFeature': ɵɵCopyDefinitionFeature,
- 'ɵɵInheritDefinitionFeature': ɵɵInheritDefinitionFeature,
- 'ɵɵInputTransformsFeature': ɵɵInputTransformsFeature,
- 'ɵɵStandaloneFeature': ɵɵStandaloneFeature,
- 'ɵɵnextContext': ɵɵnextContext,
- 'ɵɵnamespaceHTML': ɵɵnamespaceHTML,
- 'ɵɵnamespaceMathML': ɵɵnamespaceMathML,
- 'ɵɵnamespaceSVG': ɵɵnamespaceSVG,
- 'ɵɵenableBindings': ɵɵenableBindings,
- 'ɵɵdisableBindings': ɵɵdisableBindings,
- 'ɵɵelementStart': ɵɵelementStart,
- 'ɵɵelementEnd': ɵɵelementEnd,
- 'ɵɵelement': ɵɵelement,
- 'ɵɵelementContainerStart': ɵɵelementContainerStart,
- 'ɵɵelementContainerEnd': ɵɵelementContainerEnd,
- 'ɵɵelementContainer': ɵɵelementContainer,
- 'ɵɵpureFunction0': ɵɵpureFunction0,
- 'ɵɵpureFunction1': ɵɵpureFunction1,
- 'ɵɵpureFunction2': ɵɵpureFunction2,
- 'ɵɵpureFunction3': ɵɵpureFunction3,
- 'ɵɵpureFunction4': ɵɵpureFunction4,
- 'ɵɵpureFunction5': ɵɵpureFunction5,
- 'ɵɵpureFunction6': ɵɵpureFunction6,
- 'ɵɵpureFunction7': ɵɵpureFunction7,
- 'ɵɵpureFunction8': ɵɵpureFunction8,
- 'ɵɵpureFunctionV': ɵɵpureFunctionV,
- 'ɵɵgetCurrentView': ɵɵgetCurrentView,
- 'ɵɵrestoreView': ɵɵrestoreView,
- 'ɵɵlistener': ɵɵlistener,
- 'ɵɵprojection': ɵɵprojection,
- 'ɵɵsyntheticHostProperty': ɵɵsyntheticHostProperty,
- 'ɵɵsyntheticHostListener': ɵɵsyntheticHostListener,
- 'ɵɵpipeBind1': ɵɵpipeBind1,
- 'ɵɵpipeBind2': ɵɵpipeBind2,
- 'ɵɵpipeBind3': ɵɵpipeBind3,
- 'ɵɵpipeBind4': ɵɵpipeBind4,
- 'ɵɵpipeBindV': ɵɵpipeBindV,
- 'ɵɵprojectionDef': ɵɵprojectionDef,
- 'ɵɵhostProperty': ɵɵhostProperty,
- 'ɵɵproperty': ɵɵproperty,
- 'ɵɵpropertyInterpolate': ɵɵpropertyInterpolate,
- 'ɵɵpropertyInterpolate1': ɵɵpropertyInterpolate1,
- 'ɵɵpropertyInterpolate2': ɵɵpropertyInterpolate2,
- 'ɵɵpropertyInterpolate3': ɵɵpropertyInterpolate3,
- 'ɵɵpropertyInterpolate4': ɵɵpropertyInterpolate4,
- 'ɵɵpropertyInterpolate5': ɵɵpropertyInterpolate5,
- 'ɵɵpropertyInterpolate6': ɵɵpropertyInterpolate6,
- 'ɵɵpropertyInterpolate7': ɵɵpropertyInterpolate7,
- 'ɵɵpropertyInterpolate8': ɵɵpropertyInterpolate8,
- 'ɵɵpropertyInterpolateV': ɵɵpropertyInterpolateV,
- 'ɵɵpipe': ɵɵpipe,
- 'ɵɵqueryRefresh': ɵɵqueryRefresh,
- 'ɵɵviewQuery': ɵɵviewQuery,
- 'ɵɵloadQuery': ɵɵloadQuery,
- 'ɵɵcontentQuery': ɵɵcontentQuery,
- 'ɵɵreference': ɵɵreference,
- 'ɵɵclassMap': ɵɵclassMap,
- 'ɵɵclassMapInterpolate1': ɵɵclassMapInterpolate1,
- 'ɵɵclassMapInterpolate2': ɵɵclassMapInterpolate2,
- 'ɵɵclassMapInterpolate3': ɵɵclassMapInterpolate3,
- 'ɵɵclassMapInterpolate4': ɵɵclassMapInterpolate4,
- 'ɵɵclassMapInterpolate5': ɵɵclassMapInterpolate5,
- 'ɵɵclassMapInterpolate6': ɵɵclassMapInterpolate6,
- 'ɵɵclassMapInterpolate7': ɵɵclassMapInterpolate7,
- 'ɵɵclassMapInterpolate8': ɵɵclassMapInterpolate8,
- 'ɵɵclassMapInterpolateV': ɵɵclassMapInterpolateV,
- 'ɵɵstyleMap': ɵɵstyleMap,
- 'ɵɵstyleMapInterpolate1': ɵɵstyleMapInterpolate1,
- 'ɵɵstyleMapInterpolate2': ɵɵstyleMapInterpolate2,
- 'ɵɵstyleMapInterpolate3': ɵɵstyleMapInterpolate3,
- 'ɵɵstyleMapInterpolate4': ɵɵstyleMapInterpolate4,
- 'ɵɵstyleMapInterpolate5': ɵɵstyleMapInterpolate5,
- 'ɵɵstyleMapInterpolate6': ɵɵstyleMapInterpolate6,
- 'ɵɵstyleMapInterpolate7': ɵɵstyleMapInterpolate7,
- 'ɵɵstyleMapInterpolate8': ɵɵstyleMapInterpolate8,
- 'ɵɵstyleMapInterpolateV': ɵɵstyleMapInterpolateV,
- 'ɵɵstyleProp': ɵɵstyleProp,
- 'ɵɵstylePropInterpolate1': ɵɵstylePropInterpolate1,
- 'ɵɵstylePropInterpolate2': ɵɵstylePropInterpolate2,
- 'ɵɵstylePropInterpolate3': ɵɵstylePropInterpolate3,
- 'ɵɵstylePropInterpolate4': ɵɵstylePropInterpolate4,
- 'ɵɵstylePropInterpolate5': ɵɵstylePropInterpolate5,
- 'ɵɵstylePropInterpolate6': ɵɵstylePropInterpolate6,
- 'ɵɵstylePropInterpolate7': ɵɵstylePropInterpolate7,
- 'ɵɵstylePropInterpolate8': ɵɵstylePropInterpolate8,
- 'ɵɵstylePropInterpolateV': ɵɵstylePropInterpolateV,
- 'ɵɵclassProp': ɵɵclassProp,
- 'ɵɵadvance': ɵɵadvance,
- 'ɵɵtemplate': ɵɵtemplate,
- 'ɵɵdefer': ɵɵdefer,
- 'ɵɵtext': ɵɵtext,
- 'ɵɵtextInterpolate': ɵɵtextInterpolate,
- 'ɵɵtextInterpolate1': ɵɵtextInterpolate1,
- 'ɵɵtextInterpolate2': ɵɵtextInterpolate2,
- 'ɵɵtextInterpolate3': ɵɵtextInterpolate3,
- 'ɵɵtextInterpolate4': ɵɵtextInterpolate4,
- 'ɵɵtextInterpolate5': ɵɵtextInterpolate5,
- 'ɵɵtextInterpolate6': ɵɵtextInterpolate6,
- 'ɵɵtextInterpolate7': ɵɵtextInterpolate7,
- 'ɵɵtextInterpolate8': ɵɵtextInterpolate8,
- 'ɵɵtextInterpolateV': ɵɵtextInterpolateV,
- 'ɵɵi18n': ɵɵi18n,
- 'ɵɵi18nAttributes': ɵɵi18nAttributes,
- 'ɵɵi18nExp': ɵɵi18nExp,
- 'ɵɵi18nStart': ɵɵi18nStart,
- 'ɵɵi18nEnd': ɵɵi18nEnd,
- 'ɵɵi18nApply': ɵɵi18nApply,
- 'ɵɵi18nPostprocess': ɵɵi18nPostprocess,
- 'ɵɵresolveWindow': ɵɵresolveWindow,
- 'ɵɵresolveDocument': ɵɵresolveDocument,
- 'ɵɵresolveBody': ɵɵresolveBody,
- 'ɵɵsetComponentScope': ɵɵsetComponentScope,
- 'ɵɵsetNgModuleScope': ɵɵsetNgModuleScope,
- 'ɵɵregisterNgModuleType': registerNgModuleType,
- 'ɵɵsanitizeHtml': ɵɵsanitizeHtml,
- 'ɵɵsanitizeStyle': ɵɵsanitizeStyle,
- 'ɵɵsanitizeResourceUrl': ɵɵsanitizeResourceUrl,
- 'ɵɵsanitizeScript': ɵɵsanitizeScript,
- 'ɵɵsanitizeUrl': ɵɵsanitizeUrl,
- 'ɵɵsanitizeUrlOrResourceUrl': ɵɵsanitizeUrlOrResourceUrl,
- 'ɵɵtrustConstantHtml': ɵɵtrustConstantHtml,
- 'ɵɵtrustConstantResourceUrl': ɵɵtrustConstantResourceUrl,
- 'ɵɵvalidateIframeAttribute': ɵɵvalidateIframeAttribute,
- 'forwardRef': forwardRef,
- 'resolveForwardRef': resolveForwardRef,
- }))();
- function patchModuleCompilation() {
- // Does nothing, but exists as a target for patching.
- }
- function isModuleWithProviders$1(value) {
- return value.ngModule !== undefined;
- }
- function isNgModule$1(value) {
- return !!getNgModuleDef(value);
- }
- function isPipe(value) {
- return !!getPipeDef$1(value);
- }
- function isDirective(value) {
- return !!getDirectiveDef(value);
- }
- function isComponent(value) {
- return !!getComponentDef$1(value);
- }
- const moduleQueue = [];
- /**
- * Enqueues moduleDef to be checked later to see if scope can be set on its
- * component declarations.
- */
- function enqueueModuleForDelayedScoping(moduleType, ngModule) {
- moduleQueue.push({ moduleType, ngModule });
- }
- let flushingModuleQueue = false;
- /**
- * Loops over queued module definitions, if a given module definition has all of its
- * declarations resolved, it dequeues that module definition and sets the scope on
- * its declarations.
- */
- function flushModuleScopingQueueAsMuchAsPossible() {
- if (!flushingModuleQueue) {
- flushingModuleQueue = true;
- try {
- for (let i = moduleQueue.length - 1; i >= 0; i--) {
- const { moduleType, ngModule } = moduleQueue[i];
- if (ngModule.declarations && ngModule.declarations.every(isResolvedDeclaration)) {
- // dequeue
- moduleQueue.splice(i, 1);
- setScopeOnDeclaredComponents(moduleType, ngModule);
- }
- }
- }
- finally {
- flushingModuleQueue = false;
- }
- }
- }
- /**
- * Returns truthy if a declaration has resolved. If the declaration happens to be
- * an array of declarations, it will recurse to check each declaration in that array
- * (which may also be arrays).
- */
- function isResolvedDeclaration(declaration) {
- if (Array.isArray(declaration)) {
- return declaration.every(isResolvedDeclaration);
- }
- return !!resolveForwardRef(declaration);
- }
- /**
- * Compiles a module in JIT mode.
- *
- * This function automatically gets called when a class has a `@NgModule` decorator.
- */
- function compileNgModule(moduleType, ngModule = {}) {
- patchModuleCompilation();
- compileNgModuleDefs(moduleType, ngModule);
- if (ngModule.id !== undefined) {
- registerNgModuleType(moduleType, ngModule.id);
- }
- // Because we don't know if all declarations have resolved yet at the moment the
- // NgModule decorator is executing, we're enqueueing the setting of module scope
- // on its declarations to be run at a later time when all declarations for the module,
- // including forward refs, have resolved.
- enqueueModuleForDelayedScoping(moduleType, ngModule);
- }
- /**
- * Compiles and adds the `ɵmod`, `ɵfac` and `ɵinj` properties to the module class.
- *
- * It's possible to compile a module via this API which will allow duplicate declarations in its
- * root.
- */
- function compileNgModuleDefs(moduleType, ngModule, allowDuplicateDeclarationsInRoot = false) {
- ngDevMode && assertDefined(moduleType, 'Required value moduleType');
- ngDevMode && assertDefined(ngModule, 'Required value ngModule');
- const declarations = flatten$1(ngModule.declarations || EMPTY_ARRAY);
- let ngModuleDef = null;
- Object.defineProperty(moduleType, NG_MOD_DEF, {
- configurable: true,
- get: () => {
- if (ngModuleDef === null) {
- if (ngDevMode && ngModule.imports && ngModule.imports.indexOf(moduleType) > -1) {
- // We need to assert this immediately, because allowing it to continue will cause it to
- // go into an infinite loop before we've reached the point where we throw all the errors.
- throw new Error(`'${stringifyForError(moduleType)}' module can't import itself`);
- }
- const compiler = getCompilerFacade({ usage: 0 /* JitCompilerUsage.Decorator */, kind: 'NgModule', type: moduleType });
- ngModuleDef = compiler.compileNgModule(angularCoreEnv, `ng:///${moduleType.name}/ɵmod.js`, {
- type: moduleType,
- bootstrap: flatten$1(ngModule.bootstrap || EMPTY_ARRAY).map(resolveForwardRef),
- declarations: declarations.map(resolveForwardRef),
- imports: flatten$1(ngModule.imports || EMPTY_ARRAY)
- .map(resolveForwardRef)
- .map(expandModuleWithProviders),
- exports: flatten$1(ngModule.exports || EMPTY_ARRAY)
- .map(resolveForwardRef)
- .map(expandModuleWithProviders),
- schemas: ngModule.schemas ? flatten$1(ngModule.schemas) : null,
- id: ngModule.id || null,
- });
- // Set `schemas` on ngModuleDef to an empty array in JIT mode to indicate that runtime
- // should verify that there are no unknown elements in a template. In AOT mode, that check
- // happens at compile time and `schemas` information is not present on Component and Module
- // defs after compilation (so the check doesn't happen the second time at runtime).
- if (!ngModuleDef.schemas) {
- ngModuleDef.schemas = [];
- }
- }
- return ngModuleDef;
- }
- });
- let ngFactoryDef = null;
- Object.defineProperty(moduleType, NG_FACTORY_DEF, {
- get: () => {
- if (ngFactoryDef === null) {
- const compiler = getCompilerFacade({ usage: 0 /* JitCompilerUsage.Decorator */, kind: 'NgModule', type: moduleType });
- ngFactoryDef = compiler.compileFactory(angularCoreEnv, `ng:///${moduleType.name}/ɵfac.js`, {
- name: moduleType.name,
- type: moduleType,
- deps: reflectDependencies(moduleType),
- target: compiler.FactoryTarget.NgModule,
- typeArgumentCount: 0,
- });
- }
- return ngFactoryDef;
- },
- // Make the property configurable in dev mode to allow overriding in tests
- configurable: !!ngDevMode,
- });
- let ngInjectorDef = null;
- Object.defineProperty(moduleType, NG_INJ_DEF, {
- get: () => {
- if (ngInjectorDef === null) {
- ngDevMode && verifySemanticsOfNgModuleDef(moduleType, allowDuplicateDeclarationsInRoot);
- const meta = {
- name: moduleType.name,
- type: moduleType,
- providers: ngModule.providers || EMPTY_ARRAY,
- imports: [
- (ngModule.imports || EMPTY_ARRAY).map(resolveForwardRef),
- (ngModule.exports || EMPTY_ARRAY).map(resolveForwardRef),
- ],
- };
- const compiler = getCompilerFacade({ usage: 0 /* JitCompilerUsage.Decorator */, kind: 'NgModule', type: moduleType });
- ngInjectorDef =
- compiler.compileInjector(angularCoreEnv, `ng:///${moduleType.name}/ɵinj.js`, meta);
- }
- return ngInjectorDef;
- },
- // Make the property configurable in dev mode to allow overriding in tests
- configurable: !!ngDevMode,
- });
- }
- function generateStandaloneInDeclarationsError(type, location) {
- const prefix = `Unexpected "${stringifyForError(type)}" found in the "declarations" array of the`;
- const suffix = `"${stringifyForError(type)}" is marked as standalone and can't be declared ` +
- 'in any NgModule - did you intend to import it instead (by adding it to the "imports" array)?';
- return `${prefix} ${location}, ${suffix}`;
- }
- function verifySemanticsOfNgModuleDef(moduleType, allowDuplicateDeclarationsInRoot, importingModule) {
- if (verifiedNgModule.get(moduleType))
- return;
- // skip verifications of standalone components, directives, and pipes
- if (isStandalone(moduleType))
- return;
- verifiedNgModule.set(moduleType, true);
- moduleType = resolveForwardRef(moduleType);
- let ngModuleDef;
- if (importingModule) {
- ngModuleDef = getNgModuleDef(moduleType);
- if (!ngModuleDef) {
- throw new Error(`Unexpected value '${moduleType.name}' imported by the module '${importingModule.name}'. Please add an @NgModule annotation.`);
- }
- }
- else {
- ngModuleDef = getNgModuleDef(moduleType, true);
- }
- const errors = [];
- const declarations = maybeUnwrapFn$1(ngModuleDef.declarations);
- const imports = maybeUnwrapFn$1(ngModuleDef.imports);
- flatten$1(imports).map(unwrapModuleWithProvidersImports).forEach(modOrStandaloneCmpt => {
- verifySemanticsOfNgModuleImport(modOrStandaloneCmpt, moduleType);
- verifySemanticsOfNgModuleDef(modOrStandaloneCmpt, false, moduleType);
- });
- const exports = maybeUnwrapFn$1(ngModuleDef.exports);
- declarations.forEach(verifyDeclarationsHaveDefinitions);
- declarations.forEach(verifyDirectivesHaveSelector);
- declarations.forEach((declarationType) => verifyNotStandalone(declarationType, moduleType));
- const combinedDeclarations = [
- ...declarations.map(resolveForwardRef),
- ...flatten$1(imports.map(computeCombinedExports)).map(resolveForwardRef),
- ];
- exports.forEach(verifyExportsAreDeclaredOrReExported);
- declarations.forEach(decl => verifyDeclarationIsUnique(decl, allowDuplicateDeclarationsInRoot));
- const ngModule = getAnnotation(moduleType, 'NgModule');
- if (ngModule) {
- ngModule.imports &&
- flatten$1(ngModule.imports).map(unwrapModuleWithProvidersImports).forEach(mod => {
- verifySemanticsOfNgModuleImport(mod, moduleType);
- verifySemanticsOfNgModuleDef(mod, false, moduleType);
- });
- ngModule.bootstrap && deepForEach(ngModule.bootstrap, verifyCorrectBootstrapType);
- ngModule.bootstrap && deepForEach(ngModule.bootstrap, verifyComponentIsPartOfNgModule);
- }
- // Throw Error if any errors were detected.
- if (errors.length) {
- throw new Error(errors.join('\n'));
- }
- ////////////////////////////////////////////////////////////////////////////////////////////////
- function verifyDeclarationsHaveDefinitions(type) {
- type = resolveForwardRef(type);
- const def = getComponentDef$1(type) || getDirectiveDef(type) || getPipeDef$1(type);
- if (!def) {
- errors.push(`Unexpected value '${stringifyForError(type)}' declared by the module '${stringifyForError(moduleType)}'. Please add a @Pipe/@Directive/@Component annotation.`);
- }
- }
- function verifyDirectivesHaveSelector(type) {
- type = resolveForwardRef(type);
- const def = getDirectiveDef(type);
- if (!getComponentDef$1(type) && def && def.selectors.length == 0) {
- errors.push(`Directive ${stringifyForError(type)} has no selector, please add it!`);
- }
- }
- function verifyNotStandalone(type, moduleType) {
- type = resolveForwardRef(type);
- const def = getComponentDef$1(type) || getDirectiveDef(type) || getPipeDef$1(type);
- if (def?.standalone) {
- const location = `"${stringifyForError(moduleType)}" NgModule`;
- errors.push(generateStandaloneInDeclarationsError(type, location));
- }
- }
- function verifyExportsAreDeclaredOrReExported(type) {
- type = resolveForwardRef(type);
- const kind = getComponentDef$1(type) && 'component' || getDirectiveDef(type) && 'directive' ||
- getPipeDef$1(type) && 'pipe';
- if (kind) {
- // only checked if we are declared as Component, Directive, or Pipe
- // Modules don't need to be declared or imported.
- if (combinedDeclarations.lastIndexOf(type) === -1) {
- // We are exporting something which we don't explicitly declare or import.
- errors.push(`Can't export ${kind} ${stringifyForError(type)} from ${stringifyForError(moduleType)} as it was neither declared nor imported!`);
- }
- }
- }
- function verifyDeclarationIsUnique(type, suppressErrors) {
- type = resolveForwardRef(type);
- const existingModule = ownerNgModule.get(type);
- if (existingModule && existingModule !== moduleType) {
- if (!suppressErrors) {
- const modules = [existingModule, moduleType].map(stringifyForError).sort();
- errors.push(`Type ${stringifyForError(type)} is part of the declarations of 2 modules: ${modules[0]} and ${modules[1]}! ` +
- `Please consider moving ${stringifyForError(type)} to a higher module that imports ${modules[0]} and ${modules[1]}. ` +
- `You can also create a new NgModule that exports and includes ${stringifyForError(type)} then import that NgModule in ${modules[0]} and ${modules[1]}.`);
- }
- }
- else {
- // Mark type as having owner.
- ownerNgModule.set(type, moduleType);
- }
- }
- function verifyComponentIsPartOfNgModule(type) {
- type = resolveForwardRef(type);
- const existingModule = ownerNgModule.get(type);
- if (!existingModule && !isStandalone(type)) {
- errors.push(`Component ${stringifyForError(type)} is not part of any NgModule or the module has not been imported into your module.`);
- }
- }
- function verifyCorrectBootstrapType(type) {
- type = resolveForwardRef(type);
- if (!getComponentDef$1(type)) {
- errors.push(`${stringifyForError(type)} cannot be used as an entry component.`);
- }
- if (isStandalone(type)) {
- // Note: this error should be the same as the
- // `NGMODULE_BOOTSTRAP_IS_STANDALONE` one in AOT compiler.
- errors.push(`The \`${stringifyForError(type)}\` class is a standalone component, which can ` +
- `not be used in the \`@NgModule.bootstrap\` array. Use the \`bootstrapApplication\` ` +
- `function for bootstrap instead.`);
- }
- }
- function verifySemanticsOfNgModuleImport(type, importingModule) {
- type = resolveForwardRef(type);
- const directiveDef = getComponentDef$1(type) || getDirectiveDef(type);
- if (directiveDef !== null && !directiveDef.standalone) {
- throw new Error(`Unexpected directive '${type.name}' imported by the module '${importingModule.name}'. Please add an @NgModule annotation.`);
- }
- const pipeDef = getPipeDef$1(type);
- if (pipeDef !== null && !pipeDef.standalone) {
- throw new Error(`Unexpected pipe '${type.name}' imported by the module '${importingModule.name}'. Please add an @NgModule annotation.`);
- }
- }
- }
- function unwrapModuleWithProvidersImports(typeOrWithProviders) {
- typeOrWithProviders = resolveForwardRef(typeOrWithProviders);
- return typeOrWithProviders.ngModule || typeOrWithProviders;
- }
- function getAnnotation(type, name) {
- let annotation = null;
- collect(type.__annotations__);
- collect(type.decorators);
- return annotation;
- function collect(annotations) {
- if (annotations) {
- annotations.forEach(readAnnotation);
- }
- }
- function readAnnotation(decorator) {
- if (!annotation) {
- const proto = Object.getPrototypeOf(decorator);
- if (proto.ngMetadataName == name) {
- annotation = decorator;
- }
- else if (decorator.type) {
- const proto = Object.getPrototypeOf(decorator.type);
- if (proto.ngMetadataName == name) {
- annotation = decorator.args[0];
- }
- }
- }
- }
- }
- /**
- * Keep track of compiled components. This is needed because in tests we often want to compile the
- * same component with more than one NgModule. This would cause an error unless we reset which
- * NgModule the component belongs to. We keep the list of compiled components here so that the
- * TestBed can reset it later.
- */
- let ownerNgModule = new WeakMap();
- let verifiedNgModule = new WeakMap();
- function resetCompiledComponents() {
- ownerNgModule = new WeakMap();
- verifiedNgModule = new WeakMap();
- moduleQueue.length = 0;
- GENERATED_COMP_IDS.clear();
- }
- /**
- * Computes the combined declarations of explicit declarations, as well as declarations inherited by
- * traversing the exports of imported modules.
- * @param type
- */
- function computeCombinedExports(type) {
- type = resolveForwardRef(type);
- const ngModuleDef = getNgModuleDef(type);
- // a standalone component, directive or pipe
- if (ngModuleDef === null) {
- return [type];
- }
- return flatten$1(maybeUnwrapFn$1(ngModuleDef.exports).map((type) => {
- const ngModuleDef = getNgModuleDef(type);
- if (ngModuleDef) {
- verifySemanticsOfNgModuleDef(type, false);
- return computeCombinedExports(type);
- }
- else {
- return type;
- }
- }));
- }
- /**
- * Some declared components may be compiled asynchronously, and thus may not have their
- * ɵcmp set yet. If this is the case, then a reference to the module is written into
- * the `ngSelectorScope` property of the declared type.
- */
- function setScopeOnDeclaredComponents(moduleType, ngModule) {
- const declarations = flatten$1(ngModule.declarations || EMPTY_ARRAY);
- const transitiveScopes = transitiveScopesFor(moduleType);
- declarations.forEach(declaration => {
- declaration = resolveForwardRef(declaration);
- if (declaration.hasOwnProperty(NG_COMP_DEF)) {
- // A `ɵcmp` field exists - go ahead and patch the component directly.
- const component = declaration;
- const componentDef = getComponentDef$1(component);
- patchComponentDefWithScope(componentDef, transitiveScopes);
- }
- else if (!declaration.hasOwnProperty(NG_DIR_DEF) && !declaration.hasOwnProperty(NG_PIPE_DEF)) {
- // Set `ngSelectorScope` for future reference when the component compilation finishes.
- declaration.ngSelectorScope = moduleType;
- }
- });
- }
- /**
- * Patch the definition of a component with directives and pipes from the compilation scope of
- * a given module.
- */
- function patchComponentDefWithScope(componentDef, transitiveScopes) {
- componentDef.directiveDefs = () => Array.from(transitiveScopes.compilation.directives)
- .map(dir => dir.hasOwnProperty(NG_COMP_DEF) ? getComponentDef$1(dir) : getDirectiveDef(dir))
- .filter(def => !!def);
- componentDef.pipeDefs = () => Array.from(transitiveScopes.compilation.pipes).map(pipe => getPipeDef$1(pipe));
- componentDef.schemas = transitiveScopes.schemas;
- // Since we avoid Components/Directives/Pipes recompiling in case there are no overrides, we
- // may face a problem where previously compiled defs available to a given Component/Directive
- // are cached in TView and may become stale (in case any of these defs gets recompiled). In
- // order to avoid this problem, we force fresh TView to be created.
- componentDef.tView = null;
- }
- /**
- * Compute the pair of transitive scopes (compilation scope and exported scope) for a given type
- * (either a NgModule or a standalone component / directive / pipe).
- */
- function transitiveScopesFor(type) {
- if (isNgModule$1(type)) {
- return transitiveScopesForNgModule(type);
- }
- else if (isStandalone(type)) {
- const directiveDef = getComponentDef$1(type) || getDirectiveDef(type);
- if (directiveDef !== null) {
- return {
- schemas: null,
- compilation: {
- directives: new Set(),
- pipes: new Set(),
- },
- exported: {
- directives: new Set([type]),
- pipes: new Set(),
- },
- };
- }
- const pipeDef = getPipeDef$1(type);
- if (pipeDef !== null) {
- return {
- schemas: null,
- compilation: {
- directives: new Set(),
- pipes: new Set(),
- },
- exported: {
- directives: new Set(),
- pipes: new Set([type]),
- },
- };
- }
- }
- // TODO: change the error message to be more user-facing and take standalone into account
- throw new Error(`${type.name} does not have a module def (ɵmod property)`);
- }
- /**
- * Compute the pair of transitive scopes (compilation scope and exported scope) for a given module.
- *
- * This operation is memoized and the result is cached on the module's definition. This function can
- * be called on modules with components that have not fully compiled yet, but the result should not
- * be used until they have.
- *
- * @param moduleType module that transitive scope should be calculated for.
- */
- function transitiveScopesForNgModule(moduleType) {
- const def = getNgModuleDef(moduleType, true);
- if (def.transitiveCompileScopes !== null) {
- return def.transitiveCompileScopes;
- }
- const scopes = {
- schemas: def.schemas || null,
- compilation: {
- directives: new Set(),
- pipes: new Set(),
- },
- exported: {
- directives: new Set(),
- pipes: new Set(),
- },
- };
- maybeUnwrapFn$1(def.imports).forEach((imported) => {
- // When this module imports another, the imported module's exported directives and pipes are
- // added to the compilation scope of this module.
- const importedScope = transitiveScopesFor(imported);
- importedScope.exported.directives.forEach(entry => scopes.compilation.directives.add(entry));
- importedScope.exported.pipes.forEach(entry => scopes.compilation.pipes.add(entry));
- });
- maybeUnwrapFn$1(def.declarations).forEach(declared => {
- const declaredWithDefs = declared;
- if (getPipeDef$1(declaredWithDefs)) {
- scopes.compilation.pipes.add(declared);
- }
- else {
- // Either declared has a ɵcmp or ɵdir, or it's a component which hasn't
- // had its template compiled yet. In either case, it gets added to the compilation's
- // directives.
- scopes.compilation.directives.add(declared);
- }
- });
- maybeUnwrapFn$1(def.exports).forEach((exported) => {
- const exportedType = exported;
- // Either the type is a module, a pipe, or a component/directive (which may not have a
- // ɵcmp as it might be compiled asynchronously).
- if (isNgModule$1(exportedType)) {
- // When this module exports another, the exported module's exported directives and pipes are
- // added to both the compilation and exported scopes of this module.
- const exportedScope = transitiveScopesFor(exportedType);
- exportedScope.exported.directives.forEach(entry => {
- scopes.compilation.directives.add(entry);
- scopes.exported.directives.add(entry);
- });
- exportedScope.exported.pipes.forEach(entry => {
- scopes.compilation.pipes.add(entry);
- scopes.exported.pipes.add(entry);
- });
- }
- else if (getPipeDef$1(exportedType)) {
- scopes.exported.pipes.add(exportedType);
- }
- else {
- scopes.exported.directives.add(exportedType);
- }
- });
- def.transitiveCompileScopes = scopes;
- return scopes;
- }
- function expandModuleWithProviders(value) {
- if (isModuleWithProviders$1(value)) {
- return value.ngModule;
- }
- return value;
- }
- let _nextReferenceId = 0;
- class MetadataOverrider {
- constructor() {
- this._references = new Map();
- }
- /**
- * Creates a new instance for the given metadata class
- * based on an old instance and overrides.
- */
- overrideMetadata(metadataClass, oldMetadata, override) {
- const props = {};
- if (oldMetadata) {
- _valueProps(oldMetadata).forEach((prop) => props[prop] = oldMetadata[prop]);
- }
- if (override.set) {
- if (override.remove || override.add) {
- throw new Error(`Cannot set and add/remove ${ɵstringify(metadataClass)} at the same time!`);
- }
- setMetadata(props, override.set);
- }
- if (override.remove) {
- removeMetadata(props, override.remove, this._references);
- }
- if (override.add) {
- addMetadata(props, override.add);
- }
- return new metadataClass(props);
- }
- }
- function removeMetadata(metadata, remove, references) {
- const removeObjects = new Set();
- for (const prop in remove) {
- const removeValue = remove[prop];
- if (Array.isArray(removeValue)) {
- removeValue.forEach((value) => {
- removeObjects.add(_propHashKey(prop, value, references));
- });
- }
- else {
- removeObjects.add(_propHashKey(prop, removeValue, references));
- }
- }
- for (const prop in metadata) {
- const propValue = metadata[prop];
- if (Array.isArray(propValue)) {
- metadata[prop] = propValue.filter((value) => !removeObjects.has(_propHashKey(prop, value, references)));
- }
- else {
- if (removeObjects.has(_propHashKey(prop, propValue, references))) {
- metadata[prop] = undefined;
- }
- }
- }
- }
- function addMetadata(metadata, add) {
- for (const prop in add) {
- const addValue = add[prop];
- const propValue = metadata[prop];
- if (propValue != null && Array.isArray(propValue)) {
- metadata[prop] = propValue.concat(addValue);
- }
- else {
- metadata[prop] = addValue;
- }
- }
- }
- function setMetadata(metadata, set) {
- for (const prop in set) {
- metadata[prop] = set[prop];
- }
- }
- function _propHashKey(propName, propValue, references) {
- let nextObjectId = 0;
- const objectIds = new Map();
- const replacer = (key, value) => {
- if (value !== null && typeof value === 'object') {
- if (objectIds.has(value)) {
- return objectIds.get(value);
- }
- // Record an id for this object such that any later references use the object's id instead
- // of the object itself, in order to break cyclic pointers in objects.
- objectIds.set(value, `ɵobj#${nextObjectId++}`);
- // The first time an object is seen the object itself is serialized.
- return value;
- }
- else if (typeof value === 'function') {
- value = _serializeReference(value, references);
- }
- return value;
- };
- return `${propName}:${JSON.stringify(propValue, replacer)}`;
- }
- function _serializeReference(ref, references) {
- let id = references.get(ref);
- if (!id) {
- id = `${ɵstringify(ref)}${_nextReferenceId++}`;
- references.set(ref, id);
- }
- return id;
- }
- function _valueProps(obj) {
- const props = [];
- // regular public props
- Object.keys(obj).forEach((prop) => {
- if (!prop.startsWith('_')) {
- props.push(prop);
- }
- });
- // getters
- let proto = obj;
- while (proto = Object.getPrototypeOf(proto)) {
- Object.keys(proto).forEach((protoProp) => {
- const desc = Object.getOwnPropertyDescriptor(proto, protoProp);
- if (!protoProp.startsWith('_') && desc && 'get' in desc) {
- props.push(protoProp);
- }
- });
- }
- return props;
- }
- const reflection = new ɵReflectionCapabilities();
- /**
- * Allows to override ivy metadata for tests (via the `TestBed`).
- */
- class OverrideResolver {
- constructor() {
- this.overrides = new Map();
- this.resolved = new Map();
- }
- addOverride(type, override) {
- const overrides = this.overrides.get(type) || [];
- overrides.push(override);
- this.overrides.set(type, overrides);
- this.resolved.delete(type);
- }
- setOverrides(overrides) {
- this.overrides.clear();
- overrides.forEach(([type, override]) => {
- this.addOverride(type, override);
- });
- }
- getAnnotation(type) {
- const annotations = reflection.annotations(type);
- // Try to find the nearest known Type annotation and make sure that this annotation is an
- // instance of the type we are looking for, so we can use it for resolution. Note: there might
- // be multiple known annotations found due to the fact that Components can extend Directives (so
- // both Directive and Component annotations would be present), so we always check if the known
- // annotation has the right type.
- for (let i = annotations.length - 1; i >= 0; i--) {
- const annotation = annotations[i];
- const isKnownType = annotation instanceof Directive || annotation instanceof Component ||
- annotation instanceof Pipe || annotation instanceof NgModule;
- if (isKnownType) {
- return annotation instanceof this.type ? annotation : null;
- }
- }
- return null;
- }
- resolve(type) {
- let resolved = this.resolved.get(type) || null;
- if (!resolved) {
- resolved = this.getAnnotation(type);
- if (resolved) {
- const overrides = this.overrides.get(type);
- if (overrides) {
- const overrider = new MetadataOverrider();
- overrides.forEach(override => {
- resolved = overrider.overrideMetadata(this.type, resolved, override);
- });
- }
- }
- this.resolved.set(type, resolved);
- }
- return resolved;
- }
- }
- class DirectiveResolver extends OverrideResolver {
- get type() {
- return Directive;
- }
- }
- class ComponentResolver extends OverrideResolver {
- get type() {
- return Component;
- }
- }
- class PipeResolver extends OverrideResolver {
- get type() {
- return Pipe;
- }
- }
- class NgModuleResolver extends OverrideResolver {
- get type() {
- return NgModule;
- }
- }
- var TestingModuleOverride;
- (function (TestingModuleOverride) {
- TestingModuleOverride[TestingModuleOverride["DECLARATION"] = 0] = "DECLARATION";
- TestingModuleOverride[TestingModuleOverride["OVERRIDE_TEMPLATE"] = 1] = "OVERRIDE_TEMPLATE";
- })(TestingModuleOverride || (TestingModuleOverride = {}));
- function isTestingModuleOverride(value) {
- return value === TestingModuleOverride.DECLARATION ||
- value === TestingModuleOverride.OVERRIDE_TEMPLATE;
- }
- function assertNoStandaloneComponents(types, resolver, location) {
- types.forEach(type => {
- const component = resolver.resolve(type);
- if (component && component.standalone) {
- throw new Error(generateStandaloneInDeclarationsError(type, location));
- }
- });
- }
- class TestBedCompiler {
- constructor(platform, additionalModuleTypes) {
- this.platform = platform;
- this.additionalModuleTypes = additionalModuleTypes;
- this.originalComponentResolutionQueue = null;
- // Testing module configuration
- this.declarations = [];
- this.imports = [];
- this.providers = [];
- this.schemas = [];
- // Queues of components/directives/pipes that should be recompiled.
- this.pendingComponents = new Set();
- this.pendingDirectives = new Set();
- this.pendingPipes = new Set();
- // Keep track of all components and directives, so we can patch Providers onto defs later.
- this.seenComponents = new Set();
- this.seenDirectives = new Set();
- // Keep track of overridden modules, so that we can collect all affected ones in the module tree.
- this.overriddenModules = new Set();
- // Store resolved styles for Components that have template overrides present and `styleUrls`
- // defined at the same time.
- this.existingComponentStyles = new Map();
- this.resolvers = initResolvers();
- this.componentToModuleScope = new Map();
- // Map that keeps initial version of component/directive/pipe defs in case
- // we compile a Type again, thus overriding respective static fields. This is
- // required to make sure we restore defs to their initial states between test runs.
- // Note: one class may have multiple defs (for example: ɵmod and ɵinj in case of an
- // NgModule), store all of them in a map.
- this.initialNgDefs = new Map();
- // Array that keeps cleanup operations for initial versions of component/directive/pipe/module
- // defs in case TestBed makes changes to the originals.
- this.defCleanupOps = [];
- this._injector = null;
- this.compilerProviders = null;
- this.providerOverrides = [];
- this.rootProviderOverrides = [];
- // Overrides for injectables with `{providedIn: SomeModule}` need to be tracked and added to that
- // module's provider list.
- this.providerOverridesByModule = new Map();
- this.providerOverridesByToken = new Map();
- this.scopesWithOverriddenProviders = new Set();
- this.testModuleRef = null;
- class DynamicTestModule {
- }
- this.testModuleType = DynamicTestModule;
- }
- setCompilerProviders(providers) {
- this.compilerProviders = providers;
- this._injector = null;
- }
- configureTestingModule(moduleDef) {
- // Enqueue any compilation tasks for the directly declared component.
- if (moduleDef.declarations !== undefined) {
- // Verify that there are no standalone components
- assertNoStandaloneComponents(moduleDef.declarations, this.resolvers.component, '"TestBed.configureTestingModule" call');
- this.queueTypeArray(moduleDef.declarations, TestingModuleOverride.DECLARATION);
- this.declarations.push(...moduleDef.declarations);
- }
- // Enqueue any compilation tasks for imported modules.
- if (moduleDef.imports !== undefined) {
- this.queueTypesFromModulesArray(moduleDef.imports);
- this.imports.push(...moduleDef.imports);
- }
- if (moduleDef.providers !== undefined) {
- this.providers.push(...moduleDef.providers);
- }
- if (moduleDef.schemas !== undefined) {
- this.schemas.push(...moduleDef.schemas);
- }
- }
- overrideModule(ngModule, override) {
- this.overriddenModules.add(ngModule);
- // Compile the module right away.
- this.resolvers.module.addOverride(ngModule, override);
- const metadata = this.resolvers.module.resolve(ngModule);
- if (metadata === null) {
- throw invalidTypeError(ngModule.name, 'NgModule');
- }
- this.recompileNgModule(ngModule, metadata);
- // At this point, the module has a valid module def (ɵmod), but the override may have introduced
- // new declarations or imported modules. Ingest any possible new types and add them to the
- // current queue.
- this.queueTypesFromModulesArray([ngModule]);
- }
- overrideComponent(component, override) {
- this.verifyNoStandaloneFlagOverrides(component, override);
- this.resolvers.component.addOverride(component, override);
- this.pendingComponents.add(component);
- }
- overrideDirective(directive, override) {
- this.verifyNoStandaloneFlagOverrides(directive, override);
- this.resolvers.directive.addOverride(directive, override);
- this.pendingDirectives.add(directive);
- }
- overridePipe(pipe, override) {
- this.verifyNoStandaloneFlagOverrides(pipe, override);
- this.resolvers.pipe.addOverride(pipe, override);
- this.pendingPipes.add(pipe);
- }
- verifyNoStandaloneFlagOverrides(type, override) {
- if (override.add?.hasOwnProperty('standalone') || override.set?.hasOwnProperty('standalone') ||
- override.remove?.hasOwnProperty('standalone')) {
- throw new Error(`An override for the ${type.name} class has the \`standalone\` flag. ` +
- `Changing the \`standalone\` flag via TestBed overrides is not supported.`);
- }
- }
- overrideProvider(token, provider) {
- let providerDef;
- if (provider.useFactory !== undefined) {
- providerDef = {
- provide: token,
- useFactory: provider.useFactory,
- deps: provider.deps || [],
- multi: provider.multi
- };
- }
- else if (provider.useValue !== undefined) {
- providerDef = { provide: token, useValue: provider.useValue, multi: provider.multi };
- }
- else {
- providerDef = { provide: token };
- }
- const injectableDef = typeof token !== 'string' ? ɵgetInjectableDef(token) : null;
- const providedIn = injectableDef === null ? null : resolveForwardRef$1(injectableDef.providedIn);
- const overridesBucket = providedIn === 'root' ? this.rootProviderOverrides : this.providerOverrides;
- overridesBucket.push(providerDef);
- // Keep overrides grouped by token as well for fast lookups using token
- this.providerOverridesByToken.set(token, providerDef);
- if (injectableDef !== null && providedIn !== null && typeof providedIn !== 'string') {
- const existingOverrides = this.providerOverridesByModule.get(providedIn);
- if (existingOverrides !== undefined) {
- existingOverrides.push(providerDef);
- }
- else {
- this.providerOverridesByModule.set(providedIn, [providerDef]);
- }
- }
- }
- overrideTemplateUsingTestingModule(type, template) {
- const def = type[ɵNG_COMP_DEF];
- const hasStyleUrls = () => {
- const metadata = this.resolvers.component.resolve(type);
- return !!metadata.styleUrls && metadata.styleUrls.length > 0;
- };
- const overrideStyleUrls = !!def && !isComponentDefPendingResolution(type) && hasStyleUrls();
- // In Ivy, compiling a component does not require knowing the module providing the
- // component's scope, so overrideTemplateUsingTestingModule can be implemented purely via
- // overrideComponent. Important: overriding template requires full Component re-compilation,
- // which may fail in case styleUrls are also present (thus Component is considered as required
- // resolution). In order to avoid this, we preemptively set styleUrls to an empty array,
- // preserve current styles available on Component def and restore styles back once compilation
- // is complete.
- const override = overrideStyleUrls ? { template, styles: [], styleUrls: [] } : { template };
- this.overrideComponent(type, { set: override });
- if (overrideStyleUrls && def.styles && def.styles.length > 0) {
- this.existingComponentStyles.set(type, def.styles);
- }
- // Set the component's scope to be the testing module.
- this.componentToModuleScope.set(type, TestingModuleOverride.OVERRIDE_TEMPLATE);
- }
- async compileComponents() {
- this.clearComponentResolutionQueue();
- // Run compilers for all queued types.
- let needsAsyncResources = this.compileTypesSync();
- // compileComponents() should not be async unless it needs to be.
- if (needsAsyncResources) {
- let resourceLoader;
- let resolver = (url) => {
- if (!resourceLoader) {
- resourceLoader = this.injector.get(ResourceLoader);
- }
- return Promise.resolve(resourceLoader.get(url));
- };
- await resolveComponentResources(resolver);
- }
- }
- finalize() {
- // One last compile
- this.compileTypesSync();
- // Create the testing module itself.
- this.compileTestModule();
- this.applyTransitiveScopes();
- this.applyProviderOverrides();
- // Patch previously stored `styles` Component values (taken from ɵcmp), in case these
- // Components have `styleUrls` fields defined and template override was requested.
- this.patchComponentsWithExistingStyles();
- // Clear the componentToModuleScope map, so that future compilations don't reset the scope of
- // every component.
- this.componentToModuleScope.clear();
- const parentInjector = this.platform.injector;
- this.testModuleRef = new ɵRender3NgModuleRef(this.testModuleType, parentInjector, []);
- // ApplicationInitStatus.runInitializers() is marked @internal to core.
- // Cast it to any before accessing it.
- this.testModuleRef.injector.get(ApplicationInitStatus).runInitializers();
- // Set locale ID after running app initializers, since locale information might be updated while
- // running initializers. This is also consistent with the execution order while bootstrapping an
- // app (see `packages/core/src/application_ref.ts` file).
- const localeId = this.testModuleRef.injector.get(LOCALE_ID$1, ɵDEFAULT_LOCALE_ID);
- ɵsetLocaleId(localeId);
- return this.testModuleRef;
- }
- /**
- * @internal
- */
- _compileNgModuleSync(moduleType) {
- this.queueTypesFromModulesArray([moduleType]);
- this.compileTypesSync();
- this.applyProviderOverrides();
- this.applyProviderOverridesInScope(moduleType);
- this.applyTransitiveScopes();
- }
- /**
- * @internal
- */
- async _compileNgModuleAsync(moduleType) {
- this.queueTypesFromModulesArray([moduleType]);
- await this.compileComponents();
- this.applyProviderOverrides();
- this.applyProviderOverridesInScope(moduleType);
- this.applyTransitiveScopes();
- }
- /**
- * @internal
- */
- _getModuleResolver() {
- return this.resolvers.module;
- }
- /**
- * @internal
- */
- _getComponentFactories(moduleType) {
- return maybeUnwrapFn(moduleType.ɵmod.declarations).reduce((factories, declaration) => {
- const componentDef = declaration.ɵcmp;
- componentDef && factories.push(new ɵRender3ComponentFactory(componentDef, this.testModuleRef));
- return factories;
- }, []);
- }
- compileTypesSync() {
- // Compile all queued components, directives, pipes.
- let needsAsyncResources = false;
- this.pendingComponents.forEach(declaration => {
- needsAsyncResources = needsAsyncResources || isComponentDefPendingResolution(declaration);
- const metadata = this.resolvers.component.resolve(declaration);
- if (metadata === null) {
- throw invalidTypeError(declaration.name, 'Component');
- }
- this.maybeStoreNgDef(ɵNG_COMP_DEF, declaration);
- ɵcompileComponent(declaration, metadata);
- });
- this.pendingComponents.clear();
- this.pendingDirectives.forEach(declaration => {
- const metadata = this.resolvers.directive.resolve(declaration);
- if (metadata === null) {
- throw invalidTypeError(declaration.name, 'Directive');
- }
- this.maybeStoreNgDef(ɵNG_DIR_DEF, declaration);
- ɵcompileDirective(declaration, metadata);
- });
- this.pendingDirectives.clear();
- this.pendingPipes.forEach(declaration => {
- const metadata = this.resolvers.pipe.resolve(declaration);
- if (metadata === null) {
- throw invalidTypeError(declaration.name, 'Pipe');
- }
- this.maybeStoreNgDef(ɵNG_PIPE_DEF, declaration);
- ɵcompilePipe(declaration, metadata);
- });
- this.pendingPipes.clear();
- return needsAsyncResources;
- }
- applyTransitiveScopes() {
- if (this.overriddenModules.size > 0) {
- // Module overrides (via `TestBed.overrideModule`) might affect scopes that were previously
- // calculated and stored in `transitiveCompileScopes`. If module overrides are present,
- // collect all affected modules and reset scopes to force their re-calculation.
- const testingModuleDef = this.testModuleType[ɵNG_MOD_DEF];
- const affectedModules = this.collectModulesAffectedByOverrides(testingModuleDef.imports);
- if (affectedModules.size > 0) {
- affectedModules.forEach(moduleType => {
- this.storeFieldOfDefOnType(moduleType, ɵNG_MOD_DEF, 'transitiveCompileScopes');
- moduleType[ɵNG_MOD_DEF].transitiveCompileScopes = null;
- });
- }
- }
- const moduleToScope = new Map();
- const getScopeOfModule = (moduleType) => {
- if (!moduleToScope.has(moduleType)) {
- const isTestingModule = isTestingModuleOverride(moduleType);
- const realType = isTestingModule ? this.testModuleType : moduleType;
- moduleToScope.set(moduleType, ɵtransitiveScopesFor(realType));
- }
- return moduleToScope.get(moduleType);
- };
- this.componentToModuleScope.forEach((moduleType, componentType) => {
- const moduleScope = getScopeOfModule(moduleType);
- this.storeFieldOfDefOnType(componentType, ɵNG_COMP_DEF, 'directiveDefs');
- this.storeFieldOfDefOnType(componentType, ɵNG_COMP_DEF, 'pipeDefs');
- // `tView` that is stored on component def contains information about directives and pipes
- // that are in the scope of this component. Patching component scope will cause `tView` to be
- // changed. Store original `tView` before patching scope, so the `tView` (including scope
- // information) is restored back to its previous/original state before running next test.
- this.storeFieldOfDefOnType(componentType, ɵNG_COMP_DEF, 'tView');
- ɵpatchComponentDefWithScope(componentType.ɵcmp, moduleScope);
- });
- this.componentToModuleScope.clear();
- }
- applyProviderOverrides() {
- const maybeApplyOverrides = (field) => (type) => {
- const resolver = field === ɵNG_COMP_DEF ? this.resolvers.component : this.resolvers.directive;
- const metadata = resolver.resolve(type);
- if (this.hasProviderOverrides(metadata.providers)) {
- this.patchDefWithProviderOverrides(type, field);
- }
- };
- this.seenComponents.forEach(maybeApplyOverrides(ɵNG_COMP_DEF));
- this.seenDirectives.forEach(maybeApplyOverrides(ɵNG_DIR_DEF));
- this.seenComponents.clear();
- this.seenDirectives.clear();
- }
- /**
- * Applies provider overrides to a given type (either an NgModule or a standalone component)
- * and all imported NgModules and standalone components recursively.
- */
- applyProviderOverridesInScope(type) {
- const hasScope = isStandaloneComponent(type) || isNgModule(type);
- // The function can be re-entered recursively while inspecting dependencies
- // of an NgModule or a standalone component. Exit early if we come across a
- // type that can not have a scope (directive or pipe) or the type is already
- // processed earlier.
- if (!hasScope || this.scopesWithOverriddenProviders.has(type)) {
- return;
- }
- this.scopesWithOverriddenProviders.add(type);
- // NOTE: the line below triggers JIT compilation of the module injector,
- // which also invokes verification of the NgModule semantics, which produces
- // detailed error messages. The fact that the code relies on this line being
- // present here is suspicious and should be refactored in a way that the line
- // below can be moved (for ex. after an early exit check below).
- const injectorDef = type[ɵNG_INJ_DEF];
- // No provider overrides, exit early.
- if (this.providerOverridesByToken.size === 0)
- return;
- if (isStandaloneComponent(type)) {
- // Visit all component dependencies and override providers there.
- const def = getComponentDef(type);
- const dependencies = maybeUnwrapFn(def.dependencies ?? []);
- for (const dependency of dependencies) {
- this.applyProviderOverridesInScope(dependency);
- }
- }
- else {
- const providers = [
- ...injectorDef.providers,
- ...(this.providerOverridesByModule.get(type) || [])
- ];
- if (this.hasProviderOverrides(providers)) {
- this.maybeStoreNgDef(ɵNG_INJ_DEF, type);
- this.storeFieldOfDefOnType(type, ɵNG_INJ_DEF, 'providers');
- injectorDef.providers = this.getOverriddenProviders(providers);
- }
- // Apply provider overrides to imported modules recursively
- const moduleDef = type[ɵNG_MOD_DEF];
- const imports = maybeUnwrapFn(moduleDef.imports);
- for (const importedModule of imports) {
- this.applyProviderOverridesInScope(importedModule);
- }
- // Also override the providers on any ModuleWithProviders imports since those don't appear in
- // the moduleDef.
- for (const importedModule of flatten(injectorDef.imports)) {
- if (isModuleWithProviders(importedModule)) {
- this.defCleanupOps.push({
- object: importedModule,
- fieldName: 'providers',
- originalValue: importedModule.providers
- });
- importedModule.providers = this.getOverriddenProviders(importedModule.providers);
- }
- }
- }
- }
- patchComponentsWithExistingStyles() {
- this.existingComponentStyles.forEach((styles, type) => type[ɵNG_COMP_DEF].styles = styles);
- this.existingComponentStyles.clear();
- }
- queueTypeArray(arr, moduleType) {
- for (const value of arr) {
- if (Array.isArray(value)) {
- this.queueTypeArray(value, moduleType);
- }
- else {
- this.queueType(value, moduleType);
- }
- }
- }
- recompileNgModule(ngModule, metadata) {
- // Cache the initial ngModuleDef as it will be overwritten.
- this.maybeStoreNgDef(ɵNG_MOD_DEF, ngModule);
- this.maybeStoreNgDef(ɵNG_INJ_DEF, ngModule);
- ɵcompileNgModuleDefs(ngModule, metadata);
- }
- queueType(type, moduleType) {
- const component = this.resolvers.component.resolve(type);
- if (component) {
- // Check whether a give Type has respective NG def (ɵcmp) and compile if def is
- // missing. That might happen in case a class without any Angular decorators extends another
- // class where Component/Directive/Pipe decorator is defined.
- if (isComponentDefPendingResolution(type) || !type.hasOwnProperty(ɵNG_COMP_DEF)) {
- this.pendingComponents.add(type);
- }
- this.seenComponents.add(type);
- // Keep track of the module which declares this component, so later the component's scope
- // can be set correctly. If the component has already been recorded here, then one of several
- // cases is true:
- // * the module containing the component was imported multiple times (common).
- // * the component is declared in multiple modules (which is an error).
- // * the component was in 'declarations' of the testing module, and also in an imported module
- // in which case the module scope will be TestingModuleOverride.DECLARATION.
- // * overrideTemplateUsingTestingModule was called for the component in which case the module
- // scope will be TestingModuleOverride.OVERRIDE_TEMPLATE.
- //
- // If the component was previously in the testing module's 'declarations' (meaning the
- // current value is TestingModuleOverride.DECLARATION), then `moduleType` is the component's
- // real module, which was imported. This pattern is understood to mean that the component
- // should use its original scope, but that the testing module should also contain the
- // component in its scope.
- //
- // Note: standalone components have no associated NgModule, so the `moduleType` can be `null`.
- if (moduleType !== null &&
- (!this.componentToModuleScope.has(type) ||
- this.componentToModuleScope.get(type) === TestingModuleOverride.DECLARATION)) {
- this.componentToModuleScope.set(type, moduleType);
- }
- return;
- }
- const directive = this.resolvers.directive.resolve(type);
- if (directive) {
- if (!type.hasOwnProperty(ɵNG_DIR_DEF)) {
- this.pendingDirectives.add(type);
- }
- this.seenDirectives.add(type);
- return;
- }
- const pipe = this.resolvers.pipe.resolve(type);
- if (pipe && !type.hasOwnProperty(ɵNG_PIPE_DEF)) {
- this.pendingPipes.add(type);
- return;
- }
- }
- queueTypesFromModulesArray(arr) {
- // Because we may encounter the same NgModule or a standalone Component while processing
- // the dependencies of an NgModule or a standalone Component, we cache them in this set so we
- // can skip ones that have already been seen encountered. In some test setups, this caching
- // resulted in 10X runtime improvement.
- const processedDefs = new Set();
- const queueTypesFromModulesArrayRecur = (arr) => {
- for (const value of arr) {
- if (Array.isArray(value)) {
- queueTypesFromModulesArrayRecur(value);
- }
- else if (hasNgModuleDef(value)) {
- const def = value.ɵmod;
- if (processedDefs.has(def)) {
- continue;
- }
- processedDefs.add(def);
- // Look through declarations, imports, and exports, and queue
- // everything found there.
- this.queueTypeArray(maybeUnwrapFn(def.declarations), value);
- queueTypesFromModulesArrayRecur(maybeUnwrapFn(def.imports));
- queueTypesFromModulesArrayRecur(maybeUnwrapFn(def.exports));
- }
- else if (isModuleWithProviders(value)) {
- queueTypesFromModulesArrayRecur([value.ngModule]);
- }
- else if (isStandaloneComponent(value)) {
- this.queueType(value, null);
- const def = getComponentDef(value);
- if (processedDefs.has(def)) {
- continue;
- }
- processedDefs.add(def);
- const dependencies = maybeUnwrapFn(def.dependencies ?? []);
- dependencies.forEach((dependency) => {
- // Note: in AOT, the `dependencies` might also contain regular
- // (NgModule-based) Component, Directive and Pipes, so we handle
- // them separately and proceed with recursive process for standalone
- // Components and NgModules only.
- if (isStandaloneComponent(dependency) || hasNgModuleDef(dependency)) {
- queueTypesFromModulesArrayRecur([dependency]);
- }
- else {
- this.queueType(dependency, null);
- }
- });
- }
- }
- };
- queueTypesFromModulesArrayRecur(arr);
- }
- // When module overrides (via `TestBed.overrideModule`) are present, it might affect all modules
- // that import (even transitively) an overridden one. For all affected modules we need to
- // recalculate their scopes for a given test run and restore original scopes at the end. The goal
- // of this function is to collect all affected modules in a set for further processing. Example:
- // if we have the following module hierarchy: A -> B -> C (where `->` means `imports`) and module
- // `C` is overridden, we consider `A` and `B` as affected, since their scopes might become
- // invalidated with the override.
- collectModulesAffectedByOverrides(arr) {
- const seenModules = new Set();
- const affectedModules = new Set();
- const calcAffectedModulesRecur = (arr, path) => {
- for (const value of arr) {
- if (Array.isArray(value)) {
- // If the value is an array, just flatten it (by invoking this function recursively),
- // keeping "path" the same.
- calcAffectedModulesRecur(value, path);
- }
- else if (hasNgModuleDef(value)) {
- if (seenModules.has(value)) {
- // If we've seen this module before and it's included into "affected modules" list, mark
- // the whole path that leads to that module as affected, but do not descend into its
- // imports, since we already examined them before.
- if (affectedModules.has(value)) {
- path.forEach(item => affectedModules.add(item));
- }
- continue;
- }
- seenModules.add(value);
- if (this.overriddenModules.has(value)) {
- path.forEach(item => affectedModules.add(item));
- }
- // Examine module imports recursively to look for overridden modules.
- const moduleDef = value[ɵNG_MOD_DEF];
- calcAffectedModulesRecur(maybeUnwrapFn(moduleDef.imports), path.concat(value));
- }
- }
- };
- calcAffectedModulesRecur(arr, []);
- return affectedModules;
- }
- /**
- * Preserve an original def (such as ɵmod, ɵinj, etc) before applying an override.
- * Note: one class may have multiple defs (for example: ɵmod and ɵinj in case of
- * an NgModule). If there is a def in a set already, don't override it, since
- * an original one should be restored at the end of a test.
- */
- maybeStoreNgDef(prop, type) {
- if (!this.initialNgDefs.has(type)) {
- this.initialNgDefs.set(type, new Map());
- }
- const currentDefs = this.initialNgDefs.get(type);
- if (!currentDefs.has(prop)) {
- const currentDef = Object.getOwnPropertyDescriptor(type, prop);
- currentDefs.set(prop, currentDef);
- }
- }
- storeFieldOfDefOnType(type, defField, fieldName) {
- const def = type[defField];
- const originalValue = def[fieldName];
- this.defCleanupOps.push({ object: def, fieldName, originalValue });
- }
- /**
- * Clears current components resolution queue, but stores the state of the queue, so we can
- * restore it later. Clearing the queue is required before we try to compile components (via
- * `TestBed.compileComponents`), so that component defs are in sync with the resolution queue.
- */
- clearComponentResolutionQueue() {
- if (this.originalComponentResolutionQueue === null) {
- this.originalComponentResolutionQueue = new Map();
- }
- clearResolutionOfComponentResourcesQueue().forEach((value, key) => this.originalComponentResolutionQueue.set(key, value));
- }
- /*
- * Restores component resolution queue to the previously saved state. This operation is performed
- * as a part of restoring the state after completion of the current set of tests (that might
- * potentially mutate the state).
- */
- restoreComponentResolutionQueue() {
- if (this.originalComponentResolutionQueue !== null) {
- restoreComponentResolutionQueue(this.originalComponentResolutionQueue);
- this.originalComponentResolutionQueue = null;
- }
- }
- restoreOriginalState() {
- // Process cleanup ops in reverse order so the field's original value is restored correctly (in
- // case there were multiple overrides for the same field).
- forEachRight(this.defCleanupOps, (op) => {
- op.object[op.fieldName] = op.originalValue;
- });
- // Restore initial component/directive/pipe defs
- this.initialNgDefs.forEach((defs, type) => {
- defs.forEach((descriptor, prop) => {
- if (!descriptor) {
- // Delete operations are generally undesirable since they have performance
- // implications on objects they were applied to. In this particular case, situations
- // where this code is invoked should be quite rare to cause any noticeable impact,
- // since it's applied only to some test cases (for example when class with no
- // annotations extends some @Component) when we need to clear 'ɵcmp' field on a given
- // class to restore its original state (before applying overrides and running tests).
- delete type[prop];
- }
- else {
- Object.defineProperty(type, prop, descriptor);
- }
- });
- });
- this.initialNgDefs.clear();
- this.scopesWithOverriddenProviders.clear();
- this.restoreComponentResolutionQueue();
- // Restore the locale ID to the default value, this shouldn't be necessary but we never know
- ɵsetLocaleId(ɵDEFAULT_LOCALE_ID);
- }
- compileTestModule() {
- class RootScopeModule {
- }
- ɵcompileNgModuleDefs(RootScopeModule, {
- providers: [...this.rootProviderOverrides],
- });
- const providers = [
- provideZoneChangeDetection(),
- { provide: Compiler, useFactory: () => new R3TestCompiler(this) },
- ...this.providers,
- ...this.providerOverrides,
- ];
- const imports = [RootScopeModule, this.additionalModuleTypes, this.imports || []];
- // clang-format off
- ɵcompileNgModuleDefs(this.testModuleType, {
- declarations: this.declarations,
- imports,
- schemas: this.schemas,
- providers,
- }, /* allowDuplicateDeclarationsInRoot */ true);
- // clang-format on
- this.applyProviderOverridesInScope(this.testModuleType);
- }
- get injector() {
- if (this._injector !== null) {
- return this._injector;
- }
- const providers = [];
- const compilerOptions = this.platform.injector.get(COMPILER_OPTIONS);
- compilerOptions.forEach(opts => {
- if (opts.providers) {
- providers.push(opts.providers);
- }
- });
- if (this.compilerProviders !== null) {
- providers.push(...this.compilerProviders);
- }
- this._injector = Injector$1.create({ providers, parent: this.platform.injector });
- return this._injector;
- }
- // get overrides for a specific provider (if any)
- getSingleProviderOverrides(provider) {
- const token = getProviderToken(provider);
- return this.providerOverridesByToken.get(token) || null;
- }
- getProviderOverrides(providers) {
- if (!providers || !providers.length || this.providerOverridesByToken.size === 0)
- return [];
- // There are two flattening operations here. The inner flattenProviders() operates on the
- // metadata's providers and applies a mapping function which retrieves overrides for each
- // incoming provider. The outer flatten() then flattens the produced overrides array. If this is
- // not done, the array can contain other empty arrays (e.g. `[[], []]`) which leak into the
- // providers array and contaminate any error messages that might be generated.
- return flatten(flattenProviders(providers, (provider) => this.getSingleProviderOverrides(provider) || []));
- }
- getOverriddenProviders(providers) {
- if (!providers || !providers.length || this.providerOverridesByToken.size === 0)
- return [];
- const flattenedProviders = flattenProviders(providers);
- const overrides = this.getProviderOverrides(flattenedProviders);
- const overriddenProviders = [...flattenedProviders, ...overrides];
- const final = [];
- const seenOverriddenProviders = new Set();
- // We iterate through the list of providers in reverse order to make sure provider overrides
- // take precedence over the values defined in provider list. We also filter out all providers
- // that have overrides, keeping overridden values only. This is needed, since presence of a
- // provider with `ngOnDestroy` hook will cause this hook to be registered and invoked later.
- forEachRight(overriddenProviders, (provider) => {
- const token = getProviderToken(provider);
- if (this.providerOverridesByToken.has(token)) {
- if (!seenOverriddenProviders.has(token)) {
- seenOverriddenProviders.add(token);
- // Treat all overridden providers as `{multi: false}` (even if it's a multi-provider) to
- // make sure that provided override takes highest precedence and is not combined with
- // other instances of the same multi provider.
- final.unshift({ ...provider, multi: false });
- }
- }
- else {
- final.unshift(provider);
- }
- });
- return final;
- }
- hasProviderOverrides(providers) {
- return this.getProviderOverrides(providers).length > 0;
- }
- patchDefWithProviderOverrides(declaration, field) {
- const def = declaration[field];
- if (def && def.providersResolver) {
- this.maybeStoreNgDef(field, declaration);
- const resolver = def.providersResolver;
- const processProvidersFn = (providers) => this.getOverriddenProviders(providers);
- this.storeFieldOfDefOnType(declaration, field, 'providersResolver');
- def.providersResolver = (ngDef) => resolver(ngDef, processProvidersFn);
- }
- }
- }
- function initResolvers() {
- return {
- module: new NgModuleResolver(),
- component: new ComponentResolver(),
- directive: new DirectiveResolver(),
- pipe: new PipeResolver()
- };
- }
- function isStandaloneComponent(value) {
- const def = getComponentDef(value);
- return !!def?.standalone;
- }
- function getComponentDef(value) {
- return value.ɵcmp ?? null;
- }
- function hasNgModuleDef(value) {
- return value.hasOwnProperty('ɵmod');
- }
- function isNgModule(value) {
- return hasNgModuleDef(value);
- }
- function maybeUnwrapFn(maybeFn) {
- return maybeFn instanceof Function ? maybeFn() : maybeFn;
- }
- function flatten(values) {
- const out = [];
- values.forEach(value => {
- if (Array.isArray(value)) {
- out.push(...flatten(value));
- }
- else {
- out.push(value);
- }
- });
- return out;
- }
- function identityFn(value) {
- return value;
- }
- function flattenProviders(providers, mapFn = identityFn) {
- const out = [];
- for (let provider of providers) {
- if (ɵisEnvironmentProviders(provider)) {
- provider = provider.ɵproviders;
- }
- if (Array.isArray(provider)) {
- out.push(...flattenProviders(provider, mapFn));
- }
- else {
- out.push(mapFn(provider));
- }
- }
- return out;
- }
- function getProviderField(provider, field) {
- return provider && typeof provider === 'object' && provider[field];
- }
- function getProviderToken(provider) {
- return getProviderField(provider, 'provide') || provider;
- }
- function isModuleWithProviders(value) {
- return value.hasOwnProperty('ngModule');
- }
- function forEachRight(values, fn) {
- for (let idx = values.length - 1; idx >= 0; idx--) {
- fn(values[idx], idx);
- }
- }
- function invalidTypeError(name, expectedType) {
- return new Error(`${name} class doesn't have @${expectedType} decorator or is missing metadata.`);
- }
- class R3TestCompiler {
- constructor(testBed) {
- this.testBed = testBed;
- }
- compileModuleSync(moduleType) {
- this.testBed._compileNgModuleSync(moduleType);
- return new ɵNgModuleFactory(moduleType);
- }
- async compileModuleAsync(moduleType) {
- await this.testBed._compileNgModuleAsync(moduleType);
- return new ɵNgModuleFactory(moduleType);
- }
- compileModuleAndAllComponentsSync(moduleType) {
- const ngModuleFactory = this.compileModuleSync(moduleType);
- const componentFactories = this.testBed._getComponentFactories(moduleType);
- return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
- }
- async compileModuleAndAllComponentsAsync(moduleType) {
- const ngModuleFactory = await this.compileModuleAsync(moduleType);
- const componentFactories = this.testBed._getComponentFactories(moduleType);
- return new ModuleWithComponentFactories(ngModuleFactory, componentFactories);
- }
- clearCache() { }
- clearCacheFor(type) { }
- getModuleId(moduleType) {
- const meta = this.testBed._getModuleResolver().resolve(moduleType);
- return meta && meta.id || undefined;
- }
- }
- // The formatter and CI disagree on how this import statement should be formatted. Both try to keep
- let _nextRootElementId = 0;
- /**
- * Returns a singleton of the `TestBed` class.
- *
- * @publicApi
- */
- function getTestBed() {
- return TestBedImpl.INSTANCE;
- }
- /**
- * @description
- * Configures and initializes environment for unit testing and provides methods for
- * creating components and services in unit tests.
- *
- * TestBed is the primary api for writing unit tests for Angular applications and libraries.
- */
- class TestBedImpl {
- constructor() {
- // Properties
- this.platform = null;
- this.ngModule = null;
- this._compiler = null;
- this._testModuleRef = null;
- this._activeFixtures = [];
- /**
- * Internal-only flag to indicate whether a module
- * scoping queue has been checked and flushed already.
- * @nodoc
- */
- this.globalCompilationChecked = false;
- }
- static { this._INSTANCE = null; }
- static get INSTANCE() {
- return TestBedImpl._INSTANCE = TestBedImpl._INSTANCE || new TestBedImpl();
- }
- /**
- * Initialize the environment for testing with a compiler factory, a PlatformRef, and an
- * angular module. These are common to every test in the suite.
- *
- * This may only be called once, to set up the common providers for the current test
- * suite on the current platform. If you absolutely need to change the providers,
- * first use `resetTestEnvironment`.
- *
- * Test modules and platforms for individual platforms are available from
- * '@angular/<platform_name>/testing'.
- *
- * @publicApi
- */
- static initTestEnvironment(ngModule, platform, options) {
- const testBed = TestBedImpl.INSTANCE;
- testBed.initTestEnvironment(ngModule, platform, options);
- return testBed;
- }
- /**
- * Reset the providers for the test injector.
- *
- * @publicApi
- */
- static resetTestEnvironment() {
- TestBedImpl.INSTANCE.resetTestEnvironment();
- }
- static configureCompiler(config) {
- return TestBedImpl.INSTANCE.configureCompiler(config);
- }
- /**
- * Allows overriding default providers, directives, pipes, modules of the test injector,
- * which are defined in test_injector.js
- */
- static configureTestingModule(moduleDef) {
- return TestBedImpl.INSTANCE.configureTestingModule(moduleDef);
- }
- /**
- * Compile components with a `templateUrl` for the test's NgModule.
- * It is necessary to call this function
- * as fetching urls is asynchronous.
- */
- static compileComponents() {
- return TestBedImpl.INSTANCE.compileComponents();
- }
- static overrideModule(ngModule, override) {
- return TestBedImpl.INSTANCE.overrideModule(ngModule, override);
- }
- static overrideComponent(component, override) {
- return TestBedImpl.INSTANCE.overrideComponent(component, override);
- }
- static overrideDirective(directive, override) {
- return TestBedImpl.INSTANCE.overrideDirective(directive, override);
- }
- static overridePipe(pipe, override) {
- return TestBedImpl.INSTANCE.overridePipe(pipe, override);
- }
- static overrideTemplate(component, template) {
- return TestBedImpl.INSTANCE.overrideTemplate(component, template);
- }
- /**
- * Overrides the template of the given component, compiling the template
- * in the context of the TestingModule.
- *
- * Note: This works for JIT and AOTed components as well.
- */
- static overrideTemplateUsingTestingModule(component, template) {
- return TestBedImpl.INSTANCE.overrideTemplateUsingTestingModule(component, template);
- }
- static overrideProvider(token, provider) {
- return TestBedImpl.INSTANCE.overrideProvider(token, provider);
- }
- static inject(token, notFoundValue, flags) {
- return TestBedImpl.INSTANCE.inject(token, notFoundValue, ɵconvertToBitFlags(flags));
- }
- /** @deprecated from v9.0.0 use TestBed.inject */
- static get(token, notFoundValue = Injector$1.THROW_IF_NOT_FOUND, flags = InjectFlags$1.Default) {
- return TestBedImpl.INSTANCE.inject(token, notFoundValue, flags);
- }
- /**
- * Runs the given function in the `EnvironmentInjector` context of `TestBed`.
- *
- * @see {@link EnvironmentInjector#runInContext}
- */
- static runInInjectionContext(fn) {
- return TestBedImpl.INSTANCE.runInInjectionContext(fn);
- }
- static createComponent(component) {
- return TestBedImpl.INSTANCE.createComponent(component);
- }
- static resetTestingModule() {
- return TestBedImpl.INSTANCE.resetTestingModule();
- }
- static execute(tokens, fn, context) {
- return TestBedImpl.INSTANCE.execute(tokens, fn, context);
- }
- static get platform() {
- return TestBedImpl.INSTANCE.platform;
- }
- static get ngModule() {
- return TestBedImpl.INSTANCE.ngModule;
- }
- /**
- * Initialize the environment for testing with a compiler factory, a PlatformRef, and an
- * angular module. These are common to every test in the suite.
- *
- * This may only be called once, to set up the common providers for the current test
- * suite on the current platform. If you absolutely need to change the providers,
- * first use `resetTestEnvironment`.
- *
- * Test modules and platforms for individual platforms are available from
- * '@angular/<platform_name>/testing'.
- *
- * @publicApi
- */
- initTestEnvironment(ngModule, platform, options) {
- if (this.platform || this.ngModule) {
- throw new Error('Cannot set base providers because it has already been called');
- }
- TestBedImpl._environmentTeardownOptions = options?.teardown;
- TestBedImpl._environmentErrorOnUnknownElementsOption = options?.errorOnUnknownElements;
- TestBedImpl._environmentErrorOnUnknownPropertiesOption = options?.errorOnUnknownProperties;
- this.platform = platform;
- this.ngModule = ngModule;
- this._compiler = new TestBedCompiler(this.platform, this.ngModule);
- // TestBed does not have an API which can reliably detect the start of a test, and thus could be
- // used to track the state of the NgModule registry and reset it correctly. Instead, when we
- // know we're in a testing scenario, we disable the check for duplicate NgModule registration
- // completely.
- ɵsetAllowDuplicateNgModuleIdsForTest(true);
- }
- /**
- * Reset the providers for the test injector.
- *
- * @publicApi
- */
- resetTestEnvironment() {
- this.resetTestingModule();
- this._compiler = null;
- this.platform = null;
- this.ngModule = null;
- TestBedImpl._environmentTeardownOptions = undefined;
- ɵsetAllowDuplicateNgModuleIdsForTest(false);
- }
- resetTestingModule() {
- this.checkGlobalCompilationFinished();
- ɵresetCompiledComponents();
- if (this._compiler !== null) {
- this.compiler.restoreOriginalState();
- }
- this._compiler = new TestBedCompiler(this.platform, this.ngModule);
- // Restore the previous value of the "error on unknown elements" option
- ɵsetUnknownElementStrictMode$1(this._previousErrorOnUnknownElementsOption ?? THROW_ON_UNKNOWN_ELEMENTS_DEFAULT);
- // Restore the previous value of the "error on unknown properties" option
- ɵsetUnknownPropertyStrictMode$1(this._previousErrorOnUnknownPropertiesOption ?? THROW_ON_UNKNOWN_PROPERTIES_DEFAULT);
- // We have to chain a couple of try/finally blocks, because each step can
- // throw errors and we don't want it to interrupt the next step and we also
- // want an error to be thrown at the end.
- try {
- this.destroyActiveFixtures();
- }
- finally {
- try {
- if (this.shouldTearDownTestingModule()) {
- this.tearDownTestingModule();
- }
- }
- finally {
- this._testModuleRef = null;
- this._instanceTeardownOptions = undefined;
- this._instanceErrorOnUnknownElementsOption = undefined;
- this._instanceErrorOnUnknownPropertiesOption = undefined;
- }
- }
- return this;
- }
- configureCompiler(config) {
- if (config.useJit != null) {
- throw new Error('JIT compiler is not configurable via TestBed APIs.');
- }
- if (config.providers !== undefined) {
- this.compiler.setCompilerProviders(config.providers);
- }
- return this;
- }
- configureTestingModule(moduleDef) {
- this.assertNotInstantiated('TestBed.configureTestingModule', 'configure the test module');
- // Trigger module scoping queue flush before executing other TestBed operations in a test.
- // This is needed for the first test invocation to ensure that globally declared modules have
- // their components scoped properly. See the `checkGlobalCompilationFinished` function
- // description for additional info.
- this.checkGlobalCompilationFinished();
- // Always re-assign the options, even if they're undefined.
- // This ensures that we don't carry them between tests.
- this._instanceTeardownOptions = moduleDef.teardown;
- this._instanceErrorOnUnknownElementsOption = moduleDef.errorOnUnknownElements;
- this._instanceErrorOnUnknownPropertiesOption = moduleDef.errorOnUnknownProperties;
- // Store the current value of the strict mode option,
- // so we can restore it later
- this._previousErrorOnUnknownElementsOption = ɵgetUnknownElementStrictMode$1();
- ɵsetUnknownElementStrictMode$1(this.shouldThrowErrorOnUnknownElements());
- this._previousErrorOnUnknownPropertiesOption = ɵgetUnknownPropertyStrictMode$1();
- ɵsetUnknownPropertyStrictMode$1(this.shouldThrowErrorOnUnknownProperties());
- this.compiler.configureTestingModule(moduleDef);
- return this;
- }
- compileComponents() {
- return this.compiler.compileComponents();
- }
- inject(token, notFoundValue, flags) {
- if (token === TestBed) {
- return this;
- }
- const UNDEFINED = {};
- const result = this.testModuleRef.injector.get(token, UNDEFINED, ɵconvertToBitFlags(flags));
- return result === UNDEFINED ? this.compiler.injector.get(token, notFoundValue, flags) :
- result;
- }
- /** @deprecated from v9.0.0 use TestBed.inject */
- get(token, notFoundValue = Injector$1.THROW_IF_NOT_FOUND, flags = InjectFlags$1.Default) {
- return this.inject(token, notFoundValue, flags);
- }
- runInInjectionContext(fn) {
- return this.inject(EnvironmentInjector$1).runInContext(fn);
- }
- execute(tokens, fn, context) {
- const params = tokens.map(t => this.inject(t));
- return fn.apply(context, params);
- }
- overrideModule(ngModule, override) {
- this.assertNotInstantiated('overrideModule', 'override module metadata');
- this.compiler.overrideModule(ngModule, override);
- return this;
- }
- overrideComponent(component, override) {
- this.assertNotInstantiated('overrideComponent', 'override component metadata');
- this.compiler.overrideComponent(component, override);
- return this;
- }
- overrideTemplateUsingTestingModule(component, template) {
- this.assertNotInstantiated('TestBed.overrideTemplateUsingTestingModule', 'Cannot override template when the test module has already been instantiated');
- this.compiler.overrideTemplateUsingTestingModule(component, template);
- return this;
- }
- overrideDirective(directive, override) {
- this.assertNotInstantiated('overrideDirective', 'override directive metadata');
- this.compiler.overrideDirective(directive, override);
- return this;
- }
- overridePipe(pipe, override) {
- this.assertNotInstantiated('overridePipe', 'override pipe metadata');
- this.compiler.overridePipe(pipe, override);
- return this;
- }
- /**
- * Overwrites all providers for the given token with the given provider definition.
- */
- overrideProvider(token, provider) {
- this.assertNotInstantiated('overrideProvider', 'override provider');
- this.compiler.overrideProvider(token, provider);
- return this;
- }
- overrideTemplate(component, template) {
- return this.overrideComponent(component, { set: { template, templateUrl: null } });
- }
- createComponent(type) {
- const testComponentRenderer = this.inject(TestComponentRenderer);
- const rootElId = `root${_nextRootElementId++}`;
- testComponentRenderer.insertRootElement(rootElId);
- const componentDef = type.ɵcmp;
- if (!componentDef) {
- throw new Error(`It looks like '${ɵstringify(type)}' has not been compiled.`);
- }
- const noNgZone = this.inject(ComponentFixtureNoNgZone, false);
- const autoDetect = this.inject(ComponentFixtureAutoDetect, false);
- const ngZone = noNgZone ? null : this.inject(NgZone$1, null);
- const componentFactory = new ɵRender3ComponentFactory(componentDef);
- const initComponent = () => {
- const componentRef = componentFactory.create(Injector$1.NULL, [], `#${rootElId}`, this.testModuleRef);
- return new ComponentFixture(componentRef, ngZone, autoDetect);
- };
- const fixture = ngZone ? ngZone.run(initComponent) : initComponent();
- this._activeFixtures.push(fixture);
- return fixture;
- }
- /**
- * @internal strip this from published d.ts files due to
- * https://github.com/microsoft/TypeScript/issues/36216
- */
- get compiler() {
- if (this._compiler === null) {
- throw new Error(`Need to call TestBed.initTestEnvironment() first`);
- }
- return this._compiler;
- }
- /**
- * @internal strip this from published d.ts files due to
- * https://github.com/microsoft/TypeScript/issues/36216
- */
- get testModuleRef() {
- if (this._testModuleRef === null) {
- this._testModuleRef = this.compiler.finalize();
- }
- return this._testModuleRef;
- }
- assertNotInstantiated(methodName, methodDescription) {
- if (this._testModuleRef !== null) {
- throw new Error(`Cannot ${methodDescription} when the test module has already been instantiated. ` +
- `Make sure you are not using \`inject\` before \`${methodName}\`.`);
- }
- }
- /**
- * Check whether the module scoping queue should be flushed, and flush it if needed.
- *
- * When the TestBed is reset, it clears the JIT module compilation queue, cancelling any
- * in-progress module compilation. This creates a potential hazard - the very first time the
- * TestBed is initialized (or if it's reset without being initialized), there may be pending
- * compilations of modules declared in global scope. These compilations should be finished.
- *
- * To ensure that globally declared modules have their components scoped properly, this function
- * is called whenever TestBed is initialized or reset. The _first_ time that this happens, prior
- * to any other operations, the scoping queue is flushed.
- */
- checkGlobalCompilationFinished() {
- // Checking _testNgModuleRef is null should not be necessary, but is left in as an additional
- // guard that compilations queued in tests (after instantiation) are never flushed accidentally.
- if (!this.globalCompilationChecked && this._testModuleRef === null) {
- ɵflushModuleScopingQueueAsMuchAsPossible();
- }
- this.globalCompilationChecked = true;
- }
- destroyActiveFixtures() {
- let errorCount = 0;
- this._activeFixtures.forEach((fixture) => {
- try {
- fixture.destroy();
- }
- catch (e) {
- errorCount++;
- console.error('Error during cleanup of component', {
- component: fixture.componentInstance,
- stacktrace: e,
- });
- }
- });
- this._activeFixtures = [];
- if (errorCount > 0 && this.shouldRethrowTeardownErrors()) {
- throw Error(`${errorCount} ${(errorCount === 1 ? 'component' : 'components')} ` +
- `threw errors during cleanup`);
- }
- }
- shouldRethrowTeardownErrors() {
- const instanceOptions = this._instanceTeardownOptions;
- const environmentOptions = TestBedImpl._environmentTeardownOptions;
- // If the new teardown behavior hasn't been configured, preserve the old behavior.
- if (!instanceOptions && !environmentOptions) {
- return TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT;
- }
- // Otherwise use the configured behavior or default to rethrowing.
- return instanceOptions?.rethrowErrors ?? environmentOptions?.rethrowErrors ??
- this.shouldTearDownTestingModule();
- }
- shouldThrowErrorOnUnknownElements() {
- // Check if a configuration has been provided to throw when an unknown element is found
- return this._instanceErrorOnUnknownElementsOption ??
- TestBedImpl._environmentErrorOnUnknownElementsOption ?? THROW_ON_UNKNOWN_ELEMENTS_DEFAULT;
- }
- shouldThrowErrorOnUnknownProperties() {
- // Check if a configuration has been provided to throw when an unknown property is found
- return this._instanceErrorOnUnknownPropertiesOption ??
- TestBedImpl._environmentErrorOnUnknownPropertiesOption ??
- THROW_ON_UNKNOWN_PROPERTIES_DEFAULT;
- }
- shouldTearDownTestingModule() {
- return this._instanceTeardownOptions?.destroyAfterEach ??
- TestBedImpl._environmentTeardownOptions?.destroyAfterEach ??
- TEARDOWN_TESTING_MODULE_ON_DESTROY_DEFAULT;
- }
- tearDownTestingModule() {
- // If the module ref has already been destroyed, we won't be able to get a test renderer.
- if (this._testModuleRef === null) {
- return;
- }
- // Resolve the renderer ahead of time, because we want to remove the root elements as the very
- // last step, but the injector will be destroyed as a part of the module ref destruction.
- const testRenderer = this.inject(TestComponentRenderer);
- try {
- this._testModuleRef.destroy();
- }
- catch (e) {
- if (this.shouldRethrowTeardownErrors()) {
- throw e;
- }
- else {
- console.error('Error during cleanup of a testing module', {
- component: this._testModuleRef.instance,
- stacktrace: e,
- });
- }
- }
- finally {
- testRenderer.removeAllRootElements?.();
- }
- }
- }
- /**
- * @description
- * Configures and initializes environment for unit testing and provides methods for
- * creating components and services in unit tests.
- *
- * `TestBed` is the primary api for writing unit tests for Angular applications and libraries.
- *
- * @publicApi
- */
- const TestBed = TestBedImpl;
- /**
- * Allows injecting dependencies in `beforeEach()` and `it()`. Note: this function
- * (imported from the `@angular/core/testing` package) can **only** be used to inject dependencies
- * in tests. To inject dependencies in your application code, use the [`inject`](api/core/inject)
- * function from the `@angular/core` package instead.
- *
- * Example:
- *
- * ```
- * beforeEach(inject([Dependency, AClass], (dep, object) => {
- * // some code that uses `dep` and `object`
- * // ...
- * }));
- *
- * it('...', inject([AClass], (object) => {
- * object.doSomething();
- * expect(...);
- * })
- * ```
- *
- * @publicApi
- */
- function inject(tokens, fn) {
- const testBed = TestBedImpl.INSTANCE;
- // Not using an arrow function to preserve context passed from call site
- return function () {
- return testBed.execute(tokens, fn, this);
- };
- }
- /**
- * @publicApi
- */
- class InjectSetupWrapper {
- constructor(_moduleDef) {
- this._moduleDef = _moduleDef;
- }
- _addModule() {
- const moduleDef = this._moduleDef();
- if (moduleDef) {
- TestBedImpl.configureTestingModule(moduleDef);
- }
- }
- inject(tokens, fn) {
- const self = this;
- // Not using an arrow function to preserve context passed from call site
- return function () {
- self._addModule();
- return inject(tokens, fn).call(this);
- };
- }
- }
- function withModule(moduleDef, fn) {
- if (fn) {
- // Not using an arrow function to preserve context passed from call site
- return function () {
- const testBed = TestBedImpl.INSTANCE;
- if (moduleDef) {
- testBed.configureTestingModule(moduleDef);
- }
- return fn.apply(this);
- };
- }
- return new InjectSetupWrapper(() => moduleDef);
- }
- /**
- * Public Test Library for unit testing Angular applications. Assumes that you are running
- * with Jasmine, Mocha, or a similar framework which exports a beforeEach function and
- * allows tests to be asynchronous by either returning a promise or using a 'done' parameter.
- */
- // Reset the test providers and the fake async zone before each test.
- // We keep a guard because somehow this file can make it into a bundle and be executed
- // beforeEach is only defined when executing the tests
- globalThis.beforeEach?.(getCleanupHook(false));
- // We provide both a `beforeEach` and `afterEach`, because the updated behavior for
- // tearing down the module is supposed to run after the test so that we can associate
- // teardown errors with the correct test.
- // We keep a guard because somehow this file can make it into a bundle and be executed
- // afterEach is only defined when executing the tests
- globalThis.afterEach?.(getCleanupHook(true));
- function getCleanupHook(expectedTeardownValue) {
- return () => {
- const testBed = TestBedImpl.INSTANCE;
- if (testBed.shouldTearDownTestingModule() === expectedTeardownValue) {
- testBed.resetTestingModule();
- resetFakeAsyncZone();
- }
- };
- }
- /**
- * This API should be removed. But doing so seems to break `google3` and so it requires a bit of
- * investigation.
- *
- * A work around is to mark it as `@codeGenApi` for now and investigate later.
- *
- * @codeGenApi
- */
- // TODO(iminar): Remove this code in a safe way.
- const __core_private_testing_placeholder__ = '';
- /**
- * @module
- * @description
- * Entry point for all public APIs of the core/testing package.
- */
- /// <reference types="jasmine" />
- // This file only reexports content of the `src` folder. Keep it that way.
- // This file is not used to build this module. It is only used during editing
- /**
- * Generated bundle index. Do not edit.
- */
- export { ComponentFixture, ComponentFixtureAutoDetect, ComponentFixtureNoNgZone, InjectSetupWrapper, TestBed, TestComponentRenderer, __core_private_testing_placeholder__, async, discardPeriodicTasks, fakeAsync, flush, flushMicrotasks, getTestBed, inject, resetFakeAsyncZone, tick, waitForAsync, withModule, MetadataOverrider as ɵMetadataOverrider };
- //# sourceMappingURL=testing.mjs.map
|