{"id":19809677,"url":"https://github.com/rxdi/starter-client-side-lit-html","last_synced_at":"2025-05-01T08:31:02.516Z","repository":{"id":42068192,"uuid":"189192278","full_name":"rxdi/starter-client-side-lit-html","owner":"rxdi","description":"Client side application build with @rxdi/core, lit-html, graphql, dependency injection","archived":false,"fork":false,"pushed_at":"2024-03-03T10:50:39.000Z","size":1036,"stargazers_count":8,"open_issues_count":4,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-03-03T11:29:52.620Z","etag":null,"topics":["angular-like","apollo-graphql","custom-elements","decorators","dependency-injection","graphql","lit-element","lit-html","rxdi-core","typescript","webcomponents","webcomponents-framework"],"latest_commit_sha":null,"homepage":"https://rxdi-pwa.web.app/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rxdi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-05-29T09:18:46.000Z","updated_at":"2024-03-03T11:29:52.620Z","dependencies_parsed_at":"2023-02-06T07:55:12.946Z","dependency_job_id":null,"html_url":"https://github.com/rxdi/starter-client-side-lit-html","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxdi%2Fstarter-client-side-lit-html","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxdi%2Fstarter-client-side-lit-html/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxdi%2Fstarter-client-side-lit-html/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rxdi%2Fstarter-client-side-lit-html/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rxdi","download_url":"https://codeload.github.com/rxdi/starter-client-side-lit-html/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224245697,"owners_count":17279656,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular-like","apollo-graphql","custom-elements","decorators","dependency-injection","graphql","lit-element","lit-html","rxdi-core","typescript","webcomponents","webcomponents-framework"],"created_at":"2024-11-12T09:17:49.248Z","updated_at":"2024-11-12T09:17:49.807Z","avatar_url":"https://github.com/rxdi.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @rxdi/starter-client-lit-html\n\n## Starter project with graphql, lit-html, apollo-graphql, @vaadin/router, @webcomponents/custom-elements based on @rxdi/core\n\n## Powerful Dependency Injection inside Browser and Node using Typescript and RXJS 6\n\n---\n\n\u003e The idea behind [@rxdi](https://github.com/rxdi) is to create independent, dependency injection that can be used everywhere,\n\u003e Node and Browser with purpose also to share the same code without chainging nothing!\n\u003e First steps where with platform called [@gapi](https://github.com/Stradivario/gapi) you can check repository [@gapi/core](https://github.com/Stradivario/gapi-core).\n\u003e Then because of the needs of the platform i decided to develop this Reactive Dependency Injection container helping me build progressive applications.\n\u003e Hope you like my journey!\n\u003e Any help and suggestions are appreciated!\n\u003e Main repository [@rxdi/core](https://github.com/rxdi/rxdi-monorepo)\n\n---\n\nThis starter can be used with every component library working with WebComponent specification\nFor example web component can be created and defined using `customElements.define()`\nand imported inside the bundle\n\n```typescript\nimport 'my-component';\n```\n\nAfter the import it is globally available and can be used all over the application using the tag provided\n\nSet of webcomponents can be found at [@rxdi/ui-kit](https://github.com/rxdi/ui-kit)\n\n[Demo Application](https://rxdi-pwa.firebaseapp.com/)\n\n### Installation and basic examples:\n\n##### To start developing, run:\n\n```bash\ngit clone https://github.com/rxdi/starter-client-lit-html\n```\n\n##### Install modules:\n\n```bash\nnpm install\n```\n\n##### Optional\n\nUsing VSCode there are extensions helping productivity with lit-html\n\n[lit-plugin](https://marketplace.visualstudio.com/items?itemName=runem.lit-plugin)\n\n```bash\ncode --install-extension runem.lit-plugin\n```\n\n[lit-html](https://marketplace.visualstudio.com/items?itemName=bierner.lit-html)\n\n```bash\ncode --install-extension bierner.lit-html\n```\n\n##### Using `@gapi/cli` schematics\n\n```bash\nnpm i -g @gapi/cli\n```\n\n`gapi generate [name]` generates the specified schematic\n\n## Available Schematics:\n\n- component\n- directive\n- guard\n- module\n- provider\n- service\n\n## Options\n\n`--dry-run (alias: -d)`\n\n`--force (alias: -f)`\n\nCurrent settings for schematics are defined inside `gapi-cli.conf.yml`\nInformation can be found [here](https://github.com/Stradivario/gapi-cli/wiki/generate)\n\n```yml\nconfig:\n  schematics:\n    name: @rxdi/schematics\n    dryRun: false\n```\n\n##### Running App\n\nFor starting and building application we will use Parcel a new configuration-less web bundler [ParcelJS](https://parceljs.org/)\n\nTo install parcel type:\n\n```bash\nnpm install -g parcel-bundler\n```\n\n##### Start App\n\n```bash\nparcel ./src/index.html\n```\n\n##### Build App\n\n```bash\nparcel build ./src/index.html\n```\n\n## Simplest app\n\n#### Main starting point\n\nsrc/main.ts\n\n```typescript\nimport { Bootstrap } from '@rxdi/core';\nimport { AppModule } from './app/app.module';\n\nwindow.addEventListener('load', () =\u003e {\n  Bootstrap(AppModule, {\n    init: false,\n  }).subscribe(\n    () =\u003e console.log('App Started!'),\n    (err) =\u003e console.error(err)\n  );\n});\n\nif (module['hot']) {\n  module['hot'].dispose(() =\u003e (document.body.innerHTML = ''));\n}\n```\n\n#### App Module\n\nsrc/app/app.module.ts\n\n```typescript\nimport { Module } from '@rxdi/core';\nimport {\n  GraphqlModule,\n  InMemoryCache,\n  IntrospectionFragmentMatcher,\n} from '@rxdi/graphql-client';\nimport { RouterModule } from '@rxdi/router';\nimport { DOCUMENTS } from './@introspection/documents';\nimport { AppComponent } from './app.component';\nimport { HomeComponent } from './home/home.component';\nimport { State } from './app.state';\nimport { NavbarComponent } from './navbar/navbar.component';\nimport { FooterComponent } from './footer/footer.component';\nimport { GraphQLRequest } from 'apollo-link';\nimport { introspectionQueryResultData } from '~/@introspection/fragmentTypes';\n\n@Module({\n  components: [NavbarComponent, HomeComponent, FooterComponent],\n  imports: [\n    GraphqlModule.forRoot(\n      {\n        async onRequest(this: GraphQLRequest) {\n          return new Headers();\n        },\n        cache: new InMemoryCache({\n          fragmentMatcher: new IntrospectionFragmentMatcher({\n            introspectionQueryResultData,\n          }),\n        }),\n        uri: 'https://api.spacex.land/graphql/',\n        pubsub: 'wss://pubsub.graphql-server.com/subscriptions',\n      },\n      DOCUMENTS\n    ),\n    RouterModule.forRoot(\n      [\n        {\n          path: '/',\n          component: HomeComponent,\n        },\n        {\n          path: '/about',\n          children: () =\u003e import('./about/about.module'),\n        },\n        {\n          path: '(.*)',\n          component: 'not-found-component',\n          action: () =\u003e import('./not-found/not-found.component'),\n        },\n        //   { path: '/users/:user', component: 'x-user-profile' },\n      ],\n      { log: true }\n    ),\n  ],\n  bootstrap: [AppComponent],\n  providers: [State],\n})\nexport class AppModule {}\n```\n\n#### Base Graphql Layer\n\n```typescript\nimport { Injector } from '@rxdi/core';\nimport {\n  ApolloClient,\n  DataProxy,\n  importQuery,\n  MutationOptions,\n  QueryOptions,\n  SubscriptionOptions,\n} from '@rxdi/graphql-client';\nimport { from, Observable } from 'rxjs';\n\nimport { IMutation, IQuery, ISubscription } from '~/@introspection';\nimport { DocumentTypes } from '~/@introspection/documentTypes';\n\ntype Without\u003cT, K extends keyof T\u003e = Pick\u003cT, Exclude\u003ckeyof T, K\u003e\u003e;\n\ninterface ImportQueryMixin extends Without\u003cQueryOptions, 'query'\u003e {\n  query: DocumentTypes;\n}\n\ninterface ImportSubscriptionMixin\n  extends Without\u003cSubscriptionOptions, 'query'\u003e {\n  query: DocumentTypes;\n}\n\ninterface ImportMutationMixin extends Without\u003cMutationOptions, 'mutation'\u003e {\n  mutation: DocumentTypes;\n  update?(proxy: DataProxy, res: { data: IMutation }): void;\n}\n\nexport class BaseGraphqlLayer {\n  @Injector(ApolloClient) public graphql: ApolloClient;\n\n  query\u003cT = IQuery\u003e(options: ImportQueryMixin) {\n    options.query = importQuery(options.query);\n    return from(this.graphql.query(options as never)) as Observable\u003c{\n      data: T;\n    }\u003e;\n  }\n\n  mutate\u003cT = IMutation\u003e(options: ImportMutationMixin) {\n    options.mutation = importQuery(options.mutation);\n    return from(this.graphql.mutate(options as never)) as Observable\u003c{\n      data: T;\n    }\u003e;\n  }\n\n  subscribe\u003cT = ISubscription\u003e(options: ImportSubscriptionMixin) {\n    options.query = importQuery(options.query);\n    return from(this.graphql.subscribe(options as never)) as Observable\u003c{\n      data: T;\n    }\u003e;\n  }\n}\n```\n\n#### index.html shoud have defined `body` tag since `app-component` will be rendered inside\n\n```html\n\u003cbody\u003e\u003c/body\u003e\n```\n\n#### When RouterModule is set we can put our component `\u003crouter-component\u003e\u003c/router-component\u003e` inside `AppComponent`\n\n```html\n\u003crouter-outlet\u003e\n  \u003cnavbar-component slot=\"header\"\u003e\u003c/navbar-component\u003e\n  \u003cfooter-component slot=\"footer\"\u003e\u003c/footer-component\u003e\n\u003c/router-outlet\u003e\n```\n\n\u003e Note! Components needs to be bootstraped inside `AppModule` before using them or nothing will be rendered\n\n```typescript\n@Module({\n  components: [NavbarComponent, FooterComponent, HomeComponent],\n})\nexport class AppModule {}\n```\n\n\u003e Another way of importing modules is directly inside the Component `import './your.component.ts';`;\n\n#### App Component\n\nsrc/app/app.component.ts\n\n```typescript\nimport { Inject } from '@rxdi/core';\nimport { html, Component } from '@rxdi/lit-html';\nimport { State } from './app.state';\n\n/**\n * @customElement app-component\n */\n@Component({\n  selector: 'app-component',\n  template() {\n    return html`\n      \u003crouter-outlet\u003e\n        \u003cnavbar-component slot=\"header\"\u003e\u003c/navbar-component\u003e\n        \u003cfooter-component slot=\"footer\"\u003e\u003c/footer-component\u003e\n      \u003c/router-outlet\u003e\n    `;\n  },\n  container: document.body,\n})\nexport class AppComponent extends HTMLElement {\n  @Inject(State) private state: State;\n}\n```\n\n#### Navbar component\n\n```typescript\nimport { Router } from '@rxdi/router';\nimport {\n  html,\n  property,\n  eventOptions,\n  css,\n  LitElement,\n  Component,\n} from '@rxdi/lit-html';\n\n/**\n * @customElement navbar-component\n */\n@Component({\n  selector: 'navbar-component',\n  style: css`\n    .spacer {\n      flex: 1 3 auto;\n    }\n    .container {\n      display: flex;\n    }\n    ul {\n      list-style-type: none;\n      margin: 0;\n      padding: 0;\n      overflow: hidden;\n      background-color: #f3f3f3;\n      cursor: pointer;\n    }\n\n    li {\n      float: left;\n    }\n\n    li a {\n      display: block;\n      color: #666;\n      text-align: center;\n      padding: 14px 16px;\n      text-decoration: none;\n    }\n\n    li a:hover:not(.active) {\n      background-color: #ddd;\n    }\n\n    li a.active {\n      color: white;\n      background-color: #4caf50;\n    }\n  `,\n  template(this: NavbarComponent) {\n    return html`\n      \u003cul class=\"container\"\u003e\n        \u003cli\u003e\u003ca @click=${() =\u003e this.router.go('/')}\u003eHome\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca @click=${() =\u003e this.router.go('/about')}\u003eAbout\u003c/a\u003e\u003c/li\u003e\n        \u003cspan class=\"spacer\"\u003e\u003c/span\u003e\n        \u003cli\u003e\u003ca @click=${this.onIncrement}\u003eIncrement\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca @click=${this.onDecrement}\u003eDecrement\u003c/a\u003e\u003c/li\u003e\n        \u003cli\u003e\u003ca\u003e${this.counter}\u003c/a\u003e\u003c/li\u003e\n      \u003c/ul\u003e\n    `;\n  },\n})\nexport class NavbarComponent extends LitElement {\n  @property() counter = 0;\n\n  @Router() router: Router;\n\n  @eventOptions({ capture: true })\n  onIncrement(e: Event) {\n    this.counter++;\n  }\n\n  @eventOptions({ capture: true })\n  onDecrement(e: Event) {\n    this.counter--;\n  }\n}\n```\n\n#### About Component\n\nsrc/app/app.component.ts\n\n```typescript\nimport { html, Component, async, LitElement } from '@rxdi/lit-html';\nimport { timer } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\n/**\n * @customElement about-component\n */\n@Component({\n  selector: 'about-component',\n  template(this: AboutComponent) {\n    return html`\n      \u003cheader\u003e\n        \u003ch1\u003eAbout\u003c/h1\u003e\n      \u003c/header\u003e\n      ${async(this.timer)}\n      \u003cp\u003e\n        \u003cimg\n          src=\"https://www.w3schools.com/html/pic_trulli.jpg\"\n          alt=\"Italian Trulli\"\n        /\u003e\n      \u003c/p\u003e\n    `;\n  },\n})\nexport class AboutComponent extends LitElement {\n  private timer = timer(1, 1000).pipe(map((v) =\u003e v));\n}\n```\n\n#### Home Component\n\nsrc/app/home/home.component.ts\n\n```typescript\nimport { SpaceXService } from '@core/spacex/spacex.service';\nimport { ILaunch } from '@introspection/index';\nimport { AngularLayout, FlexLayout } from '@rhtml/modifiers';\nimport { Inject } from '@rxdi/core';\nimport {\n  async,\n  Component,\n  css,\n  html,\n  LitElement,\n  OnDestroy,\n  OnInit,\n  OnUpdate,\n} from '@rxdi/lit-html';\nimport { defer, Observable, timer } from 'rxjs';\nimport { map } from 'rxjs/operators';\n\n/**\n * @customElement home-component\n */\n@Component\u003cHomeComponent\u003e({\n  selector: 'home-component',\n  style: css`\n    .launch {\n      color: white;\n      background: #467792;\n      height: 100px;\n    }\n  `,\n  modifiers: [...FlexLayout, ...AngularLayout],\n  template(this) {\n    return html`\n      \u003ch4\u003eTime is ${async(this.timer)}\u003c/h4\u003e\n      \u003cheader\u003e\n        \u003ch1\u003eSpace X mission names:\u003c/h1\u003e\n      \u003c/header\u003e\n      \u003cdiv\n        fxLayout=\"row wrap\"\n        fxLayoutAlign=\"space-evenly stretch\"\n        fxLayoutGap=\"10px\"\n      \u003e\n        \u003cr-for .of=${this.launches}\u003e\n          \u003cr-let\n            .item=${(launch: ILaunch) =\u003e html`\n              \u003cdiv\n                class=\"launch\"\n                fxLayout=\"column\"\n                fxLayoutAlign=\"center center\"\n              \u003e\n                \u003cdiv\u003e${launch.mission_name}\u003c/div\u003e\n                \u003ca\n                  ngIf=${!!launch.links.article_link}\n                  target=\"_blank\"\n                  .href=${launch.links.article_link}\n                  \u003eArticle\u003c/a\n                \u003e\n                \u003ca\n                  ngIf=${!!launch.links.video_link}\n                  target=\"_blank\"\n                  .href=${launch.links.video_link}\n                  \u003eVideo\u003c/a\n                \u003e\n              \u003c/div\u003e\n            `}\n          \u003e\u003c/r-let\u003e\n        \u003c/r-for\u003e\n      \u003c/div\u003e\n    `;\n  },\n})\nexport class HomeComponent\n  extends LitElement\n  implements OnInit, OnDestroy, OnUpdate\n{\n  private timer = timer(100, 1000).pipe(\n    map(() =\u003e {\n      const date = new Date();\n      return [\n        [date.getHours(), 'hours'].join(' '),\n        [date.getSeconds(), ' seconds'].join(' '),\n      ].join(' - ');\n    })\n  );\n\n  @Inject(SpaceXService)\n  private spacexService: SpaceXService;\n\n  private launches: Observable\u003cILaunch[]\u003e = defer(() =\u003e\n    this.spacexService.getLaunches()\n  );\n\n  OnInit() {\n    console.log('Home component init');\n  }\n\n  OnDestroy() {\n    console.log('Home component destroyed');\n  }\n\n  OnUpdate() {\n    console.log('Home component updated');\n  }\n}\n```\n\n#### Launches past SpaceX graphql Query\n\nGet all past launches for every mission\n\n```graphql\nquery launchesPast($limit: Int) {\n  launchesPast(limit: $limit) {\n    ...LaunchFragment\n  }\n}\n```\n\nAnd graphql `fragment`\n\n```graphql\nfragment LaunchFragment on Launch {\n  mission_name\n  launch_date_local\n  launch_site {\n    site_name_long\n  }\n  links {\n    article_link\n    video_link\n  }\n  rocket {\n    rocket_name\n    second_stage {\n      payloads {\n        payload_type\n        payload_mass_kg\n        payload_mass_lbs\n      }\n    }\n  }\n  ships {\n    name\n    home_port\n    image\n  }\n}\n```\n\n#### Footer component\n\n```typescript\nimport { html, css, Component, LitElement } from '@rxdi/lit-html';\n\n/**\n * @customElement footer-component\n */\n@Component({\n  selector: 'footer-component',\n  useShadow: true,\n  style: css`\n    .footer {\n      position: fixed;\n      left: 0;\n      bottom: 0;\n      width: 100%;\n      background-color: #03a9f4;\n      color: white;\n      text-align: center;\n    }\n  `,\n  template() {\n    return html`\n      \u003cdiv class=\"footer\"\u003e\n        \u003cp\u003eFooter\u003c/p\u003e\n      \u003c/div\u003e\n    `;\n  },\n})\nexport class FooterComponent extends LitElement {}\n```\n\n#### Not fund component\n\n```typescript\nimport { html, Component, LitElement } from '@rxdi/lit-html';\n\n/**\n * @customElement not-found-component\n */\n@Component({\n  selector: 'not-found-component',\n  useShadow: true,\n  template: () =\u003e html`\n    \u003ch1\u003eNot found component!\u003c/h1\u003e\n    \u003cp\u003ePlease check your URL.\u003c/p\u003e\n  `,\n})\nexport class NotFoundComponent extends LitElement {}\n```\n\n#### Unit Testing\n\n```typescript\nimport 'jest';\nimport { Container, createTestBed } from '@rxdi/core';\nimport { State } from './app.state';\n\ndescribe('State Injectable', () =\u003e {\n  beforeAll(async () =\u003e {\n    await createTestBed({\n      imports: [],\n      providers: [State],\n    }).toPromise();\n  });\n\n  it('should be defined', (done) =\u003e {\n    expect(Container.has(State)).toBeTruthy();\n    done();\n  });\n});\n```\n\n#### Component testing\n\nCurrently this is not a good and working solution!\nI found out `@testing-library/dom` and needs to be researched!\nNo testing at the moment or if you have proposal solution [pull](https://github.com/rxdi/starter-client-side-lit-html/pulls) requests are welcomed\n\n```typescript\nimport { Container, createTestBed } from '@rxdi/core';\nimport { HomeComponent } from './home.component';\n\ndescribe('Home component tests', () =\u003e {\n  beforeAll(async () =\u003e {\n    await createTestBed({\n      imports: [],\n      components: [HomeComponent],\n    }).toPromise();\n  });\n\n  afterEach(() =\u003e {\n    // The jsdom instance is shared across test cases in a single file so reset the DOM\n    while (document.body.firstChild) {\n      document.body.removeChild(document.body.firstChild);\n    }\n  });\n\n  it('should be defined', (done) =\u003e {\n    expect(Container.has(HomeComponent)).toBeTruthy();\n    done();\n  });\n\n  it('displays greeting', () =\u003e {\n    const element = Container.get(HomeComponent);\n    element['render']();\n    document.body.appendChild(element);\n    const div = document.querySelector('home-component');\n    expect(div.textContent).toBe('');\n  });\n});\n```\n\n#### Debug testing with VSCODE\n\nGo to Debug tab and hit `Jest Test`\n\n#### Firebase deploy\n\nInstall `firebase-tools`\n\n```bash\nnpm install -g firebase-tools\n```\n\nExecute firebase init command and follow the steps\n\n```bash\nfirebase init\n```\n\n\u003e Note `dist` is the output folder of command `npm run build` aka `parcel build` when choosing your `deploy` folder you should consider put `dist`\n\nThis example is configurated to deploy with firebase so you need just to assign your `projectId`:\n\n```bash\nfirebase use --add\n```\n\n`firebase.json`\n\n```json\n{\n  \"hosting\": {\n    \"public\": \"dist\",\n    \"ignore\": [\"firebase.json\", \"**/.*\", \"**/node_modules/**\"],\n    \"rewrites\": [\n      {\n        \"source\": \"**\",\n        \"destination\": \"/index.html\"\n      }\n    ]\n  }\n}\n```\n\n`.firebaserc`\n\n```json\n{\n  \"projects\": {\n    \"staging\": \"rxdi-pwa\"\n  }\n}\n```\n\n#### Good practices\n\n\u003e Keep templates really simple and use renderer to show them instead of writing logic inside\n\nWrong\n\n```typescript\nimport { async } from '@rxdi/lit-html';\nimport { map } from 'rxjs/operators';\nimport { of } from 'rxjs';\n\nconst getCollection = async () =\u003e ({ collection: { name: '@rxdi/core' } });\n\nhtml`\n  \u003cdiv\u003e\n    ${async(\n      of(getCollection('@rxdi/core')).pipe(\n        map((o) =\u003e o.collection),\n        map((c) =\u003e c.name)\n      )\n    )}\n  \u003c/div\u003e\n`;\n```\n\n\u003e In this example the logic is testable\n\nCorrect\n\n```typescript\nimport { async } from '@rxdi/lit-html';\nimport { map } from 'rxjs/operators';\nimport { of } from 'rxjs';\n\nconst getCollection = async () =\u003e ({ collection: { name: '@rxdi/core' } });\nconst something = () =\u003e\n  of(getCollection()).pipe(\n    map((o) =\u003e o.collection),\n    map((c) =\u003e c.name)\n  );\n\nhtml` \u003cdiv\u003e${async(something)}\u003c/div\u003e `;\n```\n\n#### Wiring up multiple Injectables with single InjectionToken\n\n```typescript\nimport { Container, Injectable, InjectionToken } from '@rxdi/core';\n\nexport interface CoffeeFactoryToken {\n  make(action: CoffeeType): void;\n}\n\nexport type CoffeeType = 'cappuccino' | 'espresso';\n\nexport const CoffeeFactoryToken = new InjectionToken\u003cCoffeeFactoryToken\u003e(\n  'factories'\n);\n\n@Injectable({\n  id: CoffeeFactoryToken,\n  init: true,\n  multiple: true,\n})\nexport class CoffeeFactory1 implements CoffeeFactoryToken {\n  make(type: CoffeeType) {\n    console.log('Coffee factory 1', type);\n  }\n}\n\n@Injectable({\n  id: CoffeeFactoryToken,\n  init: true,\n  multiple: true,\n})\nexport class CoffeeFactory2 implements CoffeeFactoryToken {\n  make(type: CoffeeType) {\n    console.log('Coffee factory 2', type);\n  }\n}\n\n@Injectable({\n  id: CoffeeFactoryToken,\n  init: true,\n  multiple: true,\n})\nexport class CoffeeFactory3 implements CoffeeFactoryToken {\n  make(type: CoffeeType) {\n    console.log('Coffee factory 3', type);\n  }\n}\n\nconst factories = Container.getMany(CoffeeFactoryToken);\nfactories.forEach((factory) =\u003e factory.make('cappuccino'));\n```\n\n#### Injecting multiproviders inside Components\n\n```typescript\nimport { InjectMany, Component } from '@rxdi/core';\nimport { html, render, Component } from '@rxdi/lit-html';\nimport { CoffeeFactoryToken } from './coffee-factory';\n\n/**\n * @customElement my-web-component\n */\n@Component({\n  selector: 'my-web-component',\n})\nexport class MyWebComponent extends LitElement {\n  @InjectMany(CoffeeFactoryToken)\n  private factories: CoffeeFactoryToken;\n\n  OnInit() {\n    for (const factory of this.factories) {\n      factory.make('cappuccino');\n    }\n  }\n}\n```\n\n#### Code splitting\n\nLets create our lazy loaded module with routes\n\n```typescript\nimport { Module } from '@rxdi/core';\nimport { AboutComponent } from './about.component';\nimport { RouterModule } from '@rxdi/router';\n\n@Module({\n  imports: [\n    RouterModule.forChild([\n      {\n        path: '/',\n        component: 'x-user-home',\n      },\n      {\n        path: '/:user',\n        component: 'x-user-profile',\n      },\n    ]),\n  ],\n  bootstrap: [AboutComponent],\n})\nexport class AboutModule {}\n```\n\n#### Importing module\n\nLets import this module inside AppModule\n\n```typescript\nRouterModule.forRoot\u003cComponents\u003e([\n  {\n    path: '/',\n    component: 'home-component',\n  },\n  {\n    path: '/about',\n    component: 'about-component',\n    children: () =\u003e import('./about/about.module'),\n  },\n]);\n```\n\nPart with `children:` is really important since this will lazy load our module and load routes.\n\nFrom where this `about-component` come from ? and how we actually load it ? Here is the magic\n\nEvery `@rxdi/core` module has property `bootstrap`(check above `AboutModule`), putting Component inside, will add him automatically to `Dependency injection` and thus it will be registered inside `customComponents` collection from where Router will load it and redirect to.\n\nIf you don't define `component` property the rendered view will be empty and then you can control the view from the child module config by defining empty slash path `/`\n\n```typescript\nRouterModule.forChild([\n  {\n    path: '/',\n    component: 'x-user-home',\n  },\n]);\n```\n\nIf you define `component` property and the element present this will be the main element wrapper for all other views so it will present inside every child view.\n\nYou can define also components directly passing class instance since automaticaly decorator is creating static method `is()` returning `tag` name\n\n```typescript\nRouterModule.forChild([\n  {\n    path: '/',\n    component: XUserHomeComponent,\n  },\n]);\n```\n\n#### Router Guards\n\nDefining Guard\n\n```typescript\nimport { Injectable, OnInit } from '@rxdi/core';\nimport { Observable } from 'rxjs';\nimport {\n  CanActivateContext,\n  CanActivateCommands,\n  CanActivateResolver,\n  CanActivateRedirectResult,\n} from '@rxdi/router';\n\n@Injectable()\nexport class LoggedInGuard implements CanActivateResolver, OnInit {\n  OnInit() {}\n\n  canActivate(\n    context: CanActivateContext,\n    commands: CanActivateCommands\n  ):\n    | CanActivateRedirectResult\n    | boolean\n    | Promise\u003cboolean\u003e\n    | Observable\u003cboolean\u003e\n    | void {\n    // return commands.redirect('/')\n    // return false | true;\n    // return new Promise((r) =\u003e r(true | false));\n    // return new Observable((o) =\u003e {\n    //     o.next(false | true);\n    //     o.complete();\n    // });\n    // throw new Error('error');\n    // If everything is cool we can leave VOID\n  }\n}\n```\n\n#### Using guard\n\nGuards can be defined inside `RouterModule`\nWhen particular route resolver is triggered you will stop in this `Guard` before component is resolved\n\n```typescript\nRouterModule.forRoot\u003cComponents\u003e([\n  {\n    path: '/',\n    component: 'home-component',\n  },\n  {\n    path: '/about',\n    component: 'about-component',\n    children: () =\u003e import('./about/about.module'),\n    canActivate: LoggedInGuard,\n  },\n]);\n```\n\nNjoy!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frxdi%2Fstarter-client-side-lit-html","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frxdi%2Fstarter-client-side-lit-html","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frxdi%2Fstarter-client-side-lit-html/lists"}