Skip to content

Getting started

Mattia Franchetto edited this page Dec 27, 2016 · 2 revisions

At the core of Ninjagoat there is the ViewModel. Every ViewModel in Ninjagoat has the responsibility of triggering a request to re-render the view it has been associated with whenever something changes. If you are used to wonderful systems that will automagically change your view whenever something changes with just the power of unicorns, rainbows or convoluted digest loops then you might get disappointed. YOU are in charge of changing things, because YOU know best.

Isn't this old school? Having to trigger a render every time?

Ninjagoat currently uses React as its view engine. This means that you just need to notify that something changed, the view engine will do the rest. Not a big deal, right? In order to do that you will have to implement an interface notifying such changes. As we are all huuuuuuge fans of the excellent RxJs the way you will have to do this is by implementing an Observable in your ViewModel.

This looks quite scary, I'm not sure I understood

Don't worry about that for now, we'll get back to it at a later time. The good news is that you can just extend the ObservableViewModel<T> class and you are pretty much done. This class will require you to provide just one method: setData that will be called by the system whenever there is some new data available for your model.

@ViewModel('Hello')
class HelloViewModel extends ObservableViewModel<string> {
  public greetings: string;
  
  protected onData(data: string) {
    this.greetings = data;
}

Notice that we put the @ViewModel decorator on top of our class. This will tell the system that this is a viewmodel it can use, and that its name is Hello.

What is this onData thing? Where is my data coming from?

Ninjagoat is based on the assumption that your ViewModels will receive data from an external source. This might be the backend, a service in your application or even another ViewModel. The way Ninjagoat handles this is by having a dependency with an Observable<T> for every ViewModel. This is the guy responsible for sending the data to our system. In order to send a string to our HelloViewModel you will have to do the following:

let hello = new HelloViewModel(Observable.just("Hello, world!"));

Wait, what did I just see?

We are assuming that you have some familiarity with Rx here. If this is not the case, you can either take a look at the ReactiveX website or download the free chapter of Grokking Rx for an introduction to Rx and the concepts it conveys (disclaimer: one of the author of Ninjagoat is also a co-author of the book). To make a long story short, an Observable is like a Promise that can pass multiple values over time. Observable.just is the equivalent of Promise.resolve.

Why Observables and not Promises then?

Ninjagoat aims at creating ViewModels that can be updated multiple times during their lifetime. Instead of generating their own Promise by asking for data on the backend, they rely on somebody else to push them data coming from a backend whenever such data changes. Due to this passing a Promise would not work, as it will just send us a single model. Observable, on the other hand, can send us data multiple times, specifically anytime the backend has something new for us.

What about the view?

Glad you asked. Views in Ninjagoat are just classes that will inherit from View<T>, where T is the type of your ViewModel. In this case we can create one taylor made for us.

class HelloView extends View<HelloViewModel> {
	public render() {
		return (
			<h1>{this.viewModel.greetings}</h1>
		)
	}
}

Notice how the view will expose a property named viewModel that will be of the correct type you provided. Which means intellisense. Which means less errors. Aren't you happy with that? Everything else just follows React's rules, so as we are talking Typescript remember to save this file with the .tsx extension.

Ok, I also got the view, now what?

Now you can just render the view, by passing the ViewModel, and BOOOM! you're done.

ReactDOM.render(<HelloView viewmodel={hello} />, document.body)

What's next?

Developing single page applications usually boils down in having a setup that will be composed of two distinct parts.

  • A common part (that we will call master) in which you put all the common UI such as menus, toolbars and user details
  • A variable part in which all the action will happen, that will change according to the actions chosen by the end user
  • Optionally a part that will represent the landing page of your application (which we call the index)

Ninjagoat enforces this mechanism with its conventions.

Enter the conventions!

Ninjagoat aims at being convention-based. What this truly means is that the framework will try whenever possible to provide a consistent way to look at your code, so that it could be simpler for anyone to write and read things, because the convention will enforce where you should put them.

In order to achieve this goal, Ninjagoat provides you with a way to register your ViewModels and to bind them to a given View by using a simple convention:

  • Views will need to be put in a view folder inside your project
  • Views must have the same name of the ViewModel minus the ViewModel suffix
  • ViewModels must belong to a given area, that will correspond to a subfolder inside views. You can consider areas as a subset of your application, that might correspond to a feature.
  • The master view is always called Master and must stay on the root of your view folder
  • An optional Index view could be placed on the root of your view folder, and will serve as the landing page for your application.

In our case we can think of the HelloViewModel as being part of a greetings feature that we are writing. Due to this we can define a greetings area in our system and place our HelloView under the views/greetings subfolder.

I'm a bit confused. Can you explain this a bit more in depth?

Generally speaking your application will be comprised of many viewmodels and many views. With a project growing during time you might end up having files scattered all around, which will make navigation through your code a bit harder for newcomers. With respect to this, Ninjagoat aims at keeping your workspace tidy by organizing views into subfolders. In the end your application will have a structure similar to the following:

+--- viewmodels
|    |--- MainViewModel.ts
|    |--- IndexViewModel.ts
|    |--- NiceViewModel.ts
|    +--- CoolViewModel.ts
|    +--- AwesomeViewModel.ts
|    +--- GoatViewModel.ts
|
+--- views
     |--- Index.tsx
     |--- Master.tsx
     |--- General
     |    |--- Nice.tsx
     |    +--- Cool.tsx
     |    +--- Awesome.tsx
     |
     |--- Animals
          |--- Awesome.tsx
          |--- Goat.tsx

Why aren't the viewmodels following the same convention?

Ninjagoat does not enforce the same structure for viewmodels and views. This is because the same ViewModel can be reused across your application, and have different views. Think of a UserViewModel that can be used for either the profile editing page on an account area and for the public profile page for a profiles area.

Registering areas and ViewModels

In order to work with conventions, Ninjagoat provides a service called the ViewModelRegistry. A ViewModelRegistry allows you to define which viewodels will be used for a given area. It also has a handy shortcut for defining the master and index pages of your application. With the folder structure we defined above, for instance, a registration would be as follows:

registry
	.master(MasterViewModel)
	.index(IndexViewModel);
	
registry
	.add(NiceViewModel)
	.add(CoolViewModel)
	.add(AwesomeViewModel)
	.forArea("General");
	
registry
	.add(AwesomeViewModel)
	.add(GoatViewModel)
	.forArea("Animals");

The registry allows for multiple area registrations at the same time, but we prefer to split them by area, so that it is more clear what they should do.

Ok, got it. I know how to register viewmodels, but how can I access them on my app?

Ninjagoat currently uses react-router for handling the routing among viewmodels. By convention every page in your application matches the convention you used for registering areas and views. This means that in order to show the Goat page, bound to the GoatViewModel viewmodel for the area Animals you will have to request /animals/goat. Pretty straightforward, right?

By convention Ninjagoat provides a route in the form /<area>/<view>/<parameters>. The parameters part allow you to customize your viewmodel by passing different parameters, such as the user id for a UserViewModel, that will help you pick the right model from the server.

What about the observables?

Relax, we got this covered for you. When registering a viewmodel you can pass some other parameters to the system. One of these is what we call an Observable factory function, aka a function that when called will return an Observable to be registered with your viewmodel. Remember the HelloViewModel we defined before? In case of a registration we would write the following:

registry.add(HelloViewModel, context => Observable.just("Hello, world"))

The context parameter that you will receive in your function is an object that will wrap some information for you, more specifically:

  • The area for which your Observable needs to provide some data
  • A parameters object that will contain the additional parameters passed to your page

In the case of the parameters Ninjagoat will allow you to specify what parameters you will receive and where by using the same pattern matching rules used by react-router. These rules can be added as a third parameter during the registration.

registry.add(UserViewModel, context => UserObservable.for(context.parameters.id), ":id")

Every time you will land on a page that Ninjagoat understands, it will do the following things for you:

  • Parse the parameters of the route
  • Create a ViewModel of the given kind
  • Locate a View in the hierarchy that matches the convention
  • Obtain an Observable using the factory function
  • Register the Observable with the ViewModel
  • Render the View on the screen by passing the current ViewModel as parameter
  • Update the View on the screen every time your Observable will notify something new

Not bad for a few lines of code, right? We still have to understand some cool features we have, however.

Modules and registration

We wrote on the readme that Ninjagoat is a modular system. What this means is that you can split up your application into different modules, each one responsible for a given feature. All of these modules are run by a single entry point named a Ninjagoat Application. A Module is a class that needs to implement at least a register method.

class MainModule implements IModule {
	register(registry: IViewModelRegistry, overrides: any): void {
        registry.master(MasterViewModel);
        registry.index(IndexViewModel);
        registry
            .add(HelloViewModel, context => Observable.just("Hello, world")
            .forArea("Greetings");
    }
}

Look who's back! Our old friend IViewModelRegistry. This is the way Ninjagoat has to ask you for whatever ViewModel or area you might need to register. In the sample above we defined the master page an a greetings area, but other modules can register their own areas with their own viewmodels.

Having defined a module, registering it into an application and running it is fairly simple

let application = new ninjagoat.Application(); // Create a new application
application.register(new MainModule()); // Register one or ore modules
application.run(); // Run this puppy!

If you made it this far congratulations! You just learned how to run a Ninjagoat application! That would be all for this tutorial, but if you want to have some more insight about how Ninjagoat works don't forget to take a look at the Architecture page, or to look at the samples!