Introduction to Web Atoms Core
Tags: mvvm, typescript, xamarin, showdev
Published: 2019-01-12 17:48:01

---
title: Introduction to Web Atoms Core
published: true
description: Web Atoms is an advanced MVVM framework with View framework for HTML5 and Xamarin.Forms
tags: mvvm, typescript, xamarin, showdev
---

# Introduction

## What is web atoms?

"Web Atoms" is an advanced MVVM framework to write cross platform applications in `HTML5` and `Xamarin.Forms`. Unlike other frameworks, Web Atoms lets you divide User Interface logic in strict MVVM fashion and separates View in `HTML5` and `Xaml`. Benefit of separating User interface logic in ViewModel is you can individually unit test view model to make sure your logic is consistent across platforms.

Also everything is transpiled into JavaScript, your View Model and Services remain in JavaScript and in browser it works flawlessly.

In Xamarin.Forms, Web Atoms package written C# helps you easily host JavaScript modules inside an application and entire User Interface is hosed via JavaScript.

### Benefits of Web Atoms with Xamarin.Forms
* Small application download size
* Even Xaml views are converted to JavaScript
* Reuse existing NuGet components by exposing via services
* Host javascript on server with instant updates to apps
* No native compilation needed unless you add/modify native services written in c#
* You can use Xaml binding as well as Web Atoms's JavaScript bindings 

## Requirements

1. VS Code
2. TSLint extension
3. NodeJS with NPM
4. Visual Studio for Xamarin.Forms app [optional]
5. Gitlense [optional]

## UMD Loader

All web atoms modules are written (transpiled) as UMD module, suitable for testing in node as well as to load in browser with AMD Loader.

```html
<html>
    <head>
        <title>Web Atoms Core Samples</title>
        <!-- AMD Loader -->
        <script type="text/javascript" src="./node_modules/web-atoms-amd-loader/umd.js"></script>
    </head>
    <body>
        <script type="text/javascript">

            // map every package and its relative or absolute
            // path (from cdn)
            UMD.map("reflect-metadata", "./node_modules/reflect-metadata/Reflect.js");
            UMD.map("web-atoms-core", "./node_modules/web-atoms-core");
            UMD.map("web-atoms-samples", "./");

            // set language
            UMD.lang = "en-US";

            // Load view in entire page, this method will
            // resolve package from the above mentioned map of 
            // package to url. And it will create an instance of
            // App class and it will host the view `AppHost` in the
            // body of this document
            UMD.loadView("web-atoms-samples/dist/web/views/AppHost", true);
        </script>
    </body>
</html>
```

## Web Atoms Generator
Each html file under `src` folder is transpiled to a TypeScript file that can contains a class derived from AtomControl. This class is a view that can be loaded with `UMD.loadView` method and it can be nested inside any other view.

Similarly, each xaml file under `src` folder is transpiled to a TypeScript file, that can be used inside Xamarin.Forms application.

Please refer to generator for how to setup.

## Directory structure

```
root
  + dist // dynamically generated by compilation, ignored in git
      ...
  + src
     + model        // all models here
     + services     // all services inside this folder
     + tests        // all unit tests
     + view-models  // all view models must be placed here
     + web          // all html files must be placed inside web folder
     + xf           // all xaml files must be placed inside xf folder
```

It is important that you keep files inside `web` and `xf` folder, as module loader will replace `{platform}` variable in url to corresponding folder to load views. This will make view models completely independent of platform.

## AtomControl

AtomControl is a UI control which contains logic to render visual elements on the screen. AtomControl has an initialization lifecycle that is common in every platform. However, rendering lifecycle differs on every platform. For example, `Xamarin.Forms` has its own render lifecycle so AtomControl only does binding to properties. In `Web Browser`, it has special lifecycle to render contents. Most of the time developer does not need to worry about it as controls are created with best performance in mind.

Following properties exist on AtomControl and they are **Logically Inherited**. 

### Properties

* app (readonly)
* parent (readonly)
* data
* viewModel
* localViewModel

### Why data and viewModel properties are separate?

In most of UI framework, view model is usually set in `data` property, which leads to problems in multiple items control such as list box etc. That requires unnecessary climbing up hierarchy and get instance of parent's data property to bind. So having separate inherited `viewModel` property makes it easier to reference viewModel associated with whole page or fragment.

### What is localViewModel ?

To make reusable components easier, `localViewModel` can be used to host all logic that is only specific to the component. For example, lets say you want to create a calendar component. Local view model will contain all the logic to create list of all dates for currently displayed month and year. It will also create list of all years. All this logic will be independent of rendering logic and it can be put inside a view model which can be unit tested separately. Benefit here is, you can write a reusable view model for platform dependent component that has common logic across different platforms. So inside component, you will only write binding expressions to `localViewModel`.

## AtomViewModel

`AtomViewModel` class provides necessary services and properties to write easily extensible view models. It has `init` and `dispose` methods to initialize and dispose your view model.

```typescript
public class TaskListViewModel extends AtomViewModel {

    // dynamically inject TaskService
    @Inject
    public taskService: TaskService;

    public tasks: ITaskModel[];

    public async init(): Promise<void> {
        this.tasks = await this.taskService.loadTasks();
    }
}
```

### Watch

Since view model does not have access to user interaction updates, View usually does two way binding to a property and when it is modified by user, we have to watch for changes and update view model. It is done via `@Watch` decorator.

```typescript
public class TaskListViewModel extends AtomViewModel {

    @Inject
    public taskService: TaskService;

    public tasks: ITaskModel[];

    public search: string = null;

    public range = { start: 0, size: 10 };

    public async init(): Promise<void> {
        this.loadTasks(null);
    }

    // whenever any of search, range.start or range.size 
    // property is modified
    // automatically call this method
    @Watch
    public watchSearch(): void {
        this.loadTasks(
            this.search, this.range.start, this.range.size );
    }

    private async loadTasks(
        search: string,
        start: number,
        size: number
    ): Promise<void> {
        this.tasks = 
            await this.taskService.loadTasks(search, start, size);
    }
}
```
By convention, `@Watch` decorated method must be prefixed with `watch` word. Even if it is named differently, `@Watch` decorator will still watch for changes.

Watch decorator automatically starts watching every expression that starts with `this.`, it ignores methods and it watches only properties. For performance, this decorator does not parse javascript code, it only looks for `this.identifier.identifier...` expression and creates map of watching every single property in entire expression.

>  Every property accessed inside `@Watch` decorated method must be initialized to `non undefined` value. Since binding framework ignores `undefined`

### Watch property

Watch can also be defined on a readonly property.

```typescript

    @Watch
    public get fullName(): string {
        return `${ this.model.firstName } ${ this.model.lastName }`;
    }

```

HTML
```html
    <span text="[$viewModel.fullName]"></span>
```

XAML
```xml
    <Label Text="[$viewModel.firstName]">
```

You can bind any view property to `fullName` and it will refresh automatically whenever any changes was detected in `model.firstName` or `model.lastName`. Again, both must not initialized to `undefined`.

### Validate property

Though `@Watch` is great way to watch any property, we cannot use it for validation because as soon as page is loaded, user will be thrown with error messages. So we have created `@Validate` decorator which only returns an error message after `this.isValid` property is called.

For example,

```typescript

public class SignupViewModel extends AtomViewModel {

    @Inject
    public navigationService: NavigationService;

    public model = {
        firstName: null,
        lastName: null
    };

    // both validate properties will return undefined value
    // unless `this.isValid` is referenced.

    @Validate
    public get errorFirstName(): string {
        return this.model.firstName ? "" : "First name is required";
    }

    @Validate
    public get errorLastName(): string {
        return this.model.firstName ? "" : "Last name is required";
    }

    public signup(): Promise<void> {

        // as soon as this property is called first time
        // validation decorator will update and error will be displayed
        if (!this.isValid) {
            await this.navigationService.alert(`Please enter required fields`);
            return;
        }
    }

}

```
HTML
```html
<div view-model="{ this.resolve(SignupViewModel) }">

    <input placeholder="First name:" value="$[viewModel.model.firstName]">
    <span class="error" text="[$viewModel.errorFirstName]"></span>

    <input placeholder="Last name:" value="$[viewModel.model.lastName]">
    <span class="error" text="[$viewModel.errorLastName]"></span>

    ...

    <button event-click="{ () => $viewModel.signup() }">Signup</button>

</div>
```

XAML
```xml

    <Entry 
        Placeholder="First name:" 
        Text="$[viewModel.model.firstName]"/>
    <Label
        Style="Error" 
        Text="[$viewModel.errorFirstName]"/>

    <Entry 
        Placeholder="Last name:" 
        Text="$[viewModel.model.lastName]"/>
    <Label
        Style="Error" 
        Text="[$viewModel.errorLastName]"/>

    ...

    <Button Command="{ () => $viewModel.signup() }">Signup</Button>

```

In above example, when page is loaded, error spans will not display anything. Even if `firstName` and `lastName` both are empty. As soon as user clicks `Signup` button, `this.isValid` get method will start watching for changes in all `@Validate` decorator methods and user interface will start displaying error message.


## Dive into samples
https://www.webatoms.in/samples.html#contextId=0

{% github neurospeech/web-atoms-core %}