# Source: https://raw.githubusercontent.com/ServiceStack/docs.servicestack.net/refs/heads/main/MyApp/_pages/templates/spa-components.md

---
title: Single Page App Components
slug: templates-spa-components
---

<div class="not-prose my-8 ml-20 flex justify-center">
<svg style="max-width:200px" version="1.1" viewBox="0 0 261.76 226.69" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(1.3333 0 0 -1.3333 -76.311 313.34)"><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-75.491l98.16-170.02 98.16 170.02z" fill="#41b883"/></g><g transform="translate(178.06 235.01)"><path d="m0 0-22.669-39.264-22.669 39.264h-36.227l58.896-102.01 58.896 102.01z" fill="#34495e"/></g></g>
</svg>
<svg style="max-width:280px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 841.9 595.3" enable-background="new 0 0 841.9 595.3" xml:space="preserve">
<g>
	<path fill="#61DAFB" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4c14.4-63.6,8-114.2-20.2-130.4c-6.5-3.8-14.1-5.6-22.4-5.6v22.3
		c4.6,0,8.3,0.9,11.4,2.6c13.6,7.8,19.5,37.5,14.9,75.7c-1.1,9.4-2.9,19.3-5.1,29.4c-19.6-4.8-41-8.5-63.5-10.9
		c-13.5-18.5-27.5-35.3-41.6-50c32.6-30.3,63.2-46.9,84-46.9l0-22.3c0,0,0,0,0,0c-27.5,0-63.5,19.6-99.9,53.6
		c-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6c-14,14.7-28,31.4-41.3,49.9c-22.6,2.4-44,6.1-63.6,11
		c-2.3-10-4-19.7-5.2-29c-4.7-38.2,1.1-67.9,14.6-75.8c3-1.8,6.9-2.6,11.5-2.6l0-22.3c0,0,0,0,0,0c-8.4,0-16,1.8-22.6,5.6
		c-28.1,16.2-34.4,66.7-19.9,130.1c-62.2,19.2-102.7,49.9-102.7,82.3c0,32.5,40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4
		c6.5,3.8,14.1,5.6,22.5,5.6c27.5,0,63.5-19.6,99.9-53.6c36.4,33.8,72.4,53.2,99.9,53.2c8.4,0,16-1.8,22.6-5.6
		c28.1-16.2,34.4-66.7,19.9-130.1C625.8,359.7,666.3,328.9,666.3,296.5z M536.1,229.8c-3.7,12.9-8.3,26.2-13.5,39.5
		c-4.1-8-8.4-16-13.1-24c-4.6-8-9.5-15.8-14.4-23.4C509.3,224,523,226.6,536.1,229.8z M490.3,336.3c-7.8,13.5-15.8,26.3-24.1,38.2
		c-14.9,1.3-30,2-45.2,2c-15.1,0-30.2-0.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38c-7.6-13.1-14.5-26.4-20.8-39.8
		c6.2-13.4,13.2-26.8,20.7-39.9c7.8-13.5,15.8-26.3,24.1-38.2c14.9-1.3,30-2,45.2-2c15.1,0,30.2,0.7,45,1.9
		c8.3,11.9,16.4,24.6,24.2,38c7.6,13.1,14.5,26.4,20.8,39.8C504.7,309.8,497.8,323.2,490.3,336.3z M522.6,323.3
		c5.4,13.4,10,26.8,13.8,39.8c-13.1,3.2-26.9,5.9-41.2,8c4.9-7.7,9.8-15.6,14.4-23.7C514.2,339.4,518.5,331.3,522.6,323.3z
		 M421.2,430c-9.3-9.6-18.6-20.3-27.8-32c9,0.4,18.2,0.7,27.5,0.7c9.4,0,18.7-0.2,27.8-0.7C439.7,409.7,430.4,420.4,421.2,430z
		 M346.8,371.1c-14.2-2.1-27.9-4.7-41-7.9c3.7-12.9,8.3-26.2,13.5-39.5c4.1,8,8.4,16,13.1,24C337.1,355.7,341.9,363.5,346.8,371.1z
		 M420.7,163c9.3,9.6,18.6,20.3,27.8,32c-9-0.4-18.2-0.7-27.5-0.7c-9.4,0-18.7,0.2-27.8,0.7C402.2,183.3,411.5,172.6,420.7,163z
		 M346.7,221.9c-4.9,7.7-9.8,15.6-14.4,23.7c-4.6,8-8.9,16-13,24c-5.4-13.4-10-26.8-13.8-39.8C318.6,226.7,332.4,224,346.7,221.9z
		 M256.2,347.1c-35.4-15.1-58.3-34.9-58.3-50.6c0-15.7,22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1c5.7,19.6,13.2,40,22.5,60.9
		c-9.2,20.8-16.6,41.1-22.2,60.6C274.3,354.2,264.9,350.8,256.2,347.1z M310,490c-13.6-7.8-19.5-37.5-14.9-75.7
		c1.1-9.4,2.9-19.3,5.1-29.4c19.6,4.8,41,8.5,63.5,10.9c13.5,18.5,27.5,35.3,41.6,50c-32.6,30.3-63.2,46.9-84,46.9
		C316.8,492.6,313,491.7,310,490z M547.2,413.8c4.7,38.2-1.1,67.9-14.6,75.8c-3,1.8-6.9,2.6-11.5,2.6c-20.7,0-51.4-16.5-84-46.6
		c14-14.7,28-31.4,41.3-49.9c22.6-2.4,44-6.1,63.6-11C544.3,394.8,546.1,404.5,547.2,413.8z M585.7,347.1c-8.6,3.7-18,7-27.7,10.1
		c-5.7-19.6-13.2-40-22.5-60.9c9.2-20.8,16.6-41.1,22.2-60.6c9.9,3.1,19.3,6.5,28.1,10.2c35.4,15.1,58.3,34.9,58.3,50.6
		C644,312.2,621.1,332.1,585.7,347.1z"/>
	<polygon fill="#61DAFB" points="320.8,78.4 320.8,78.4 320.8,78.4 	"/>
	<circle fill="#61DAFB" cx="420.9" cy="296.5" r="45.7"/>
	<polygon fill="#61DAFB" points="520.5,78.1 520.5,78.1 520.5,78.1 	"/>
</g>
</svg>
<svg style="max-width:200px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
	 viewBox="0 0 250 250" xml:space="preserve">
<g>
	<polygon fill="#DD0031" points="125,30 125,30 125,30 31.9,63.2 46.1,186.3 125,230 125,230 125,230 203.9,186.3 218.1,63.2 	"/>
	<polygon fill="#C3002F" points="125,30 125,52.2 125,52.1 125,153.4 125,153.4 125,230 125,230 203.9,186.3 218.1,63.2 125,30 	"/>
	<path fill="#FFFFFF" d="M125,52.1L66.8,182.6h0h21.7h0l11.7-29.2h49.4l11.7,29.2h0h21.7h0L125,52.1L125,52.1L125,52.1L125,52.1
		L125,52.1z M142,135.4H108l17-40.9L142,135.4z"/>
</g>
</svg>
</div>


## SPA Component Libraries

To lay the foundation for richer and more tightly integrated UI controls, we've created UI and common component libraries for the 
3 most popular JS frameworks:

### [@servicestack/vue](https://github.com/ServiceStack/servicestack-vue)

### [@servicestack/react](https://github.com/ServiceStack/servicestack-react)

### [@servicestack/angular](https://github.com/ServiceStack/servicestack-angular)

All new [Single Page App Project Templates](/templates/single-page-apps) have been pre-configured to use these libraries which will make it 
a lot easier to deliver new UI components and updates to existing SPA Apps with just an npm upgrade.

### UI Component List

Currently the component libraries include common Bootstrap UI Form Controls, Navigation Components and a generic `Forbidden` page 
to handle when users don't have access to a protected route.

Side-by-side comparison displaying the names for the different Component Type in each JS Framework:

| Control        | vue              | react          | angular            |
|----------------|------------------|----------------|--------------------|
| Forbidden      | Forbidden        | Forbidden      | ForbiddenComponent |
| ErrorSummary   | error-summary    | ErrorSummary   | error-summary      |
| Input          | v-input          | Input          | ng-input           |
| Select         | v-select         | Select         |                    |
| CheckBox       | v-checkbox       | CheckBox       | ng-checkbox        |
| Button         | v-button         | Button         | ng-button          |
| SvgImage       | v-svg            | SvgImage       |                    |
| Link           | v-link           | ALink          | ng-link            |
| LinkButton     | link-button      | LinkButton     | link-button        |
| Nav            | v-nav            | Nav            |                    |
| Navbar         | navbar           | Navbar         | navbar             |
| NavLink        | nav-link         | NavLink        | nav-link           |
| NavButtonGroup | nav-button-group | NavButtonGroup | nav-button-group   |
| NavLinkButton  | nav-link-button  | NavLinkButton  | nav-link-button    |



### Bootstrap UI Form Controls

The Bootstrap UI form controls include built-in support for validation where they can render validation errors from ServiceStack's
`ResponseStatus` object, e.g the [SignIn.vue](https://github.com/NetCoreTemplates/vue-spa/blob/master/MyApp/src/components/SignIn.vue) 
page used in all Vue project templates:

```html
<form @submit.prevent="submit" :class="{ error:responseStatus, loading }" >
    <div class="form-group">
        <error-summary except="userName,password" :responseStatus="responseStatus" />
    </div>
    <div class="form-group">
        <v-input id="userName" v-model="userName" placeholder="Username" :responseStatus="responseStatus" 
                 label="Email" help="Email you signed up with" />
    </div>
    <div class="form-group">
        <v-input type="password"  id="password" v-model="password" placeholder="Password" 
                :responseStatus="responseStatus" label="Password" help="6 characters or more" />
    </div>
    <div class="form-group">
        <v-checkbox id="rememberMe" v-model="rememberMe" :responseStatus="responseStatus">
            Remember Me
        </v-checkbox>
    </div>
    <div class="form-group">
        <button type="submit" class="btn btn-lg btn-primary">Login</button>
        <link-button href="/signup" lg outline-secondary class="ml-2">Register New User</link-button>
    </div>
</form>
```

Initially renders the following UI:

![](/img/pages/spa/vue-form-components.png)

All form validation is typically performed the same way, by sending a populated ServiceStack Request DTO and capturing any 
Service Client exceptions in the components `responseStatus` property, e.g:

```typescript
protected async submit() {
    try {
        this.loading = true;
        this.responseStatus = null;
        const response = await client.post(new Authenticate({
            provider: 'credentials',
            userName: this.userName,
            password: this.password,
            rememberMe: this.rememberMe,
        }));
        bus.$emit('signin', response);
        redirect(this.$route.query.redirect as string || Routes.Home);
    } catch (e) {
        this.responseStatus = e.responseStatus || e;
    } finally {
        this.loading = false;
    }
}
```

Where it automatically applies the field validation error next to their respective control:

![](/img/pages/spa/vue-form-components-invalid.png)

Conversely you can unset the `responseStatus` to reset all form validation errors:

```ts
this.responseStatus = null;
```

### Navigation Components

All navigation components are populated the same way for all JavaScript FX's where it embeds the navigation data structure in the page by
serializing the response of the `GetNavItems` Service to JSON that's embedded in the layout page where it's only loaded once 
upon the initial page request (immediately, without an Ajax network request): 

::: v-pre
```hbs
{{#script}}
NAV_ITEMS = {{ 'GetNavItems'  |> execService |> json }};
AUTH      = {{ 'Authenticate' |> execService({ ifErrorReturn: "null" }) |> json }};
{{/script}}
```
:::

The navigation items data structure is used with new Navigation Components for each JavaScript FX to render the menu navigation
which is initially captured in a state object containing the NavItems data structure, the Users Session and the list
of User Attributes generated from the Authenticated Users Session (if any), e.g:

### Vue

In Vue the Nav and User Information is maintained in a global `store` object which uses `UserAttributes.fromSession()` from the 
`@servicestack/client` library to generate the list of User Attributes:

```ts
export const store: State = {
  nav: global.NAV_ITEMS as GetNavItemsResponse,
  userSession: global.AUTH as AuthenticateResponse,
  userAttributes: UserAttributes.fromSession(global.AUTH),
};
```

The built-in list of User Attributes include:

  - `auth` - Authenticated User
  - `role:TheRole` - Authenticated User with `TheRole` role.
  - `perm:ThePermission` - Authenticated User with `ThePermission` permission.

This list can be further extended to include your own custom User Attributes, these are used to control whether to display the navigation item 
based on if the attribute is an exact match for the `Show` and `Hide` properties of the `NavItem`. E.g. Navigation Items populated with:

```json
  "NavItems": [
    { "href":"/login",      "label":"Sign In", "hide":"auth" },
    { "href":"/profile",    "label":"Profile", "show":"auth" },
    { "href":"/admin",      "label":"Admin",   "show":"role:Admin" }
  ],
```

Will hide the **Sign In** and show the **Profile** nav items to Authenticated Users and only show the **Admin** nav item to Admin Users.

The `navbar` component uses these data structures to [render the main menu](https://github.com/NetCoreTemplates/vue-spa/blob/24ce35d67dcec0f4e00f0dd2f40cf2e8f0c58c7c/MyApp/src/App.vue#L9):

```html
<navbar :items="store.nav.results" :attributes="store.userAttributes" />
```

The rendering of the component can be further customized using any of the `NavOptions` properties, in **camelCase**.

Which also applies to the list of [registered OAuth provider buttons](https://github.com/NetCoreTemplates/vue-spa/blob/24ce35d67dcec0f4e00f0dd2f40cf2e8f0c58c7c/MyApp/src/App.vue#L9)
rendered with `<nav-button-group>`:

```html
<nav-button-group :items="store.nav.navItemsMap.auth" :attributes="store.userAttributes" 
                  :baseHref="store.nav.baseUrl" block lg />
```

In addition to `NavOptions` properties, new Bootstrap UI Controls (in each JavaScript FX) can also use these common bootstrap attributes
to stylize their components:

```ts
export declare class BootstrapBase extends Vue {
    primary?: boolean;
    outlinePrimary?: boolean;
    secondary?: boolean;
    outlineSecondary?: boolean;
    success?: boolean;
    outlineSuccess?: boolean;
    info?: boolean;
    outlineInfo?: boolean;
    warning?: boolean;
    outlineWarning?: boolean;
    danger?: boolean;
    outlineDanger?: boolean;
    light?: boolean;
    outlineLight?: boolean;
    dark?: boolean;
    outlineDark?: boolean;
    lg?: boolean;
    md?: boolean;
    sm?: boolean;
    xs?: boolean;
    block?: boolean;
    vertical?: boolean;
    horizontal?: boolean;
}
```

> camelCase properties like `outlinePrimary` are exposed as **kebab-case** in components, e.g. `outline-primary`

### React

These same components are 
[available in React](https://github.com/NetCoreTemplates/react-spa/blob/7bee202a4ceff4b5a191df178633c45f7f736073/MyApp/src/App.tsx#L46) 
from the new `@servicestack/react` library, except the JSX Components use PascalCase, e.g:

```html
<Navbar items={state.nav.results} attributes={state.userAttributes} />
```

Likewise for [NavButtonGroup](https://github.com/NetCoreTemplates/react-spa/blob/7bee202a4ceff4b5a191df178633c45f7f736073/MyApp/src/components/SignIn.tsx#L93):

```html
<NavButtonGroup items={state.nav.navItemsMap.auth} attributes={state.userAttributes} 
                baseHref={state.nav.baseUrl} block lg />
```

### Angular

Likewise for Angular from the new `@servicestack/angular` package where the main menu is rendered using the 
[navbar component](https://github.com/NetCoreTemplates/angular-spa/blob/278a965d3b8d922f6ea55408269308ff90913263/MyApp/src/app/app.component.html#L9):

```html
<navbar [items]="nav.results" [attributes]="userAttributes"></navbar>
```

And the OAuth Button list is rendered using the [nav-button-group component](https://github.com/NetCoreTemplates/angular-spa/blob/278a965d3b8d922f6ea55408269308ff90913263/MyApp/src/app/signin/index.ts#L49) in kebab-case:

```html
 <nav-button-group [items]="nav.navItemsMap.auth" [attributes]="userAttributes" 
                   [baseHref]="nav.baseUrl" block lg></nav-button-group>
```

### Mobile and Desktop Apps

Whilst there are no native components developed for different Mobile and Desktop UI's, the same navigation information can be accessed
by calling the `GetNavItems` Service, e.g:

```csharp
var response = await client.GetAsync(new GetNavItems());
```