Base template used for dew studio app, include a viewmodel logic, a services logic and utils
MIT License
Hi, I love Nativescript and I'm adopting it for my Apps. I've also have created a base project where we can start for every app we're building.
In this case, I've thought that it can be useful also for other people.
This template collect objects and stuff that we have found useful during our app development.
Lets see what we are talking about.
Actually in the app-root.xml the default page points to home (template presentation and example page). You should point to start changing the default page in root.
The home page is only as example for the use of template functions.
tns create appname --template tns-template-ava-ts
We created a custom Model, the AvaModel that should be extended by all App models because it contains some utils stuff, like the CopyFromObject methods.
This method allows you to copy and create an object of type T and resolve the problem of json object to typescript object, see this for more information.
Example:
class Mytype extends AvaModel {
public name: string = "";
public surname: string = "";
public get fullName() {
return this.name + " " + this.surname;
}
}
const json = { name: "Andrew", surname: "Keller" };
let myObject: MyType = object.assign(myObject, json);
console.log(myObject.fullName); // <--- throws error
let myObject1: MyType = new MyType().CopyFromObject<MyType>(json);
console.log(myObject1.fullName); // <--- works fine
We created a custom ViewModel, the AvaViewModel that should be extended by all App viewmodels because it contains some utils stuff, like npc function or the TokenManager (see Token Manager bottom).
npc function is a short way to notify that property is changed to the interface. For example:
// set values
this.myProp = 2;
this.myProp1 = "Andrew";
this.myProp3 = false;
// classic notification changed
this.notifyPropertyChange("myProp", this.myProp);
this.notifyPropertyChange("myProp1", this.myProp1);
this.notifyPropertyChange("myProp2", this.myProp2);
// with npc
this.npc("myProp");
// or
this.npc(["myProp1", "myProp2"]);
You have also access to a TokenManager that is useful to manage jwt access tokens for the app. You will see how it works bottom.
We have a class to manage CSS classes on view objects, I created this because is really useful for animations.
The class implements 4 methods:
I've also introduced a type of Error, AvaError that can be used to create custom Errors.
The reason we don't simply extends the Error class is that the name and type will be always of the root error type. For example if I check the typeof or the name of MyError that extends TypeError the result will be always TypeError
In this case we can throw a custom error, like this:
try {
if (something) dostuff;
else throw new AvaError("notSomething", "something is false");
} catch (err) {
if (err.name === "notSomething") {
console.log(err.message);
}
}
in the RestClient section you can see also how to use better this way to manage errors.
It is just a file with global constants for api keys and stuff like this.
The debugger class is used to create logs that depends from a Debugger setting, in this way you can set it in production to false (I suggest you in the app.ts file).
For example:
Debugger.IsDebugOn = true;
Debugger.log("I work");
Debugger.IsDebugOn = false;
Debugger.log("You can't see me!");
Another utils class is the Timing class that provides function for delays and intervals.
// delay of 5000 ms
await Timing.delay(5000);
// timeout
await Timing.timeout(() => {
dialogs.alert("5000 seconds passed");
}, 5000);
// repeat every second
let i = 0;
await Timing.repeat((handler: number) => {
i++;
if (i > 10) Timing.clearRepeat(handler);
}, 1000);
This template implements a system for user management via jwt token (more info here) where our payload is the IToken implmeneted interface:
// Generic token interface
interface IToken {
idUser: number;
updated: string;
created: string;
expired: string;
userName: string;
email: string;
// separated by commas
types: string;
// separated by commas
claims: string;
}
// token implementation
export class Token implements IToken {
public idUser: number = 0;
public updated: string = "";
public created: string = "";
public expired: string = "";
public userName: string = "";
public email: string = "";
public types: string = "";
public claims: string = "";
}
you can extend token, but the TokenManager class needs the implementation of IToken to works well.
The TokenManager class in fact needs, for some of its functions, of properties of IToken.
Let see how it works.
The Token Manager works with application settings and has methods to load, unload, check roles/claims etc.
The most important methors are:
With this three methods we can manage the token's lifecycle in our app.
We also have:
Let see an example of the token use:
export class ViewModel extends AvaViewModel {
constructor()
{
super();
}
public async pageLoaded()
{
// this load the token and return the response if the token has been loaded
if(this.tManager.loadLocalToken())
{
if(this.tManager.isAdmin())
// navigate to first admin page
else
if(this.tManager.hasRole(RolesTypes.U1))
// navigate to user page
else
// navigate to login with unauthorized message
}
else
{
// navigate to login page
}
}
}
An example of login
public async onLoginTap(args: EventData){
try
{
const client = new RestClient();
const request = await client.GET(this.apiLoginUrl);
const token = request.toStandardResponseData<Token>().data;
// set token, from now we can load it
this.tManager.setToken(token);
this.tManager.loadLocalToken();
}
catch(err)
{
console.log(err);
}
}
I came from .NET (and I still love it) and I'm using on it the DewRestClient for web requests.
For Nativescript I've created a simil (but with less functions for now) library for the Rest Client.
My first approach is about the response. All my API works with this type of response
export default class StandardResponse<T> {
public data: T;
public errorMessage!: string;
public error: StandardError = new StandardError();
}
class StandardError {
public desc: string = "";
public number: number = 0;
}
And actually I use two types of Error for this client:
export enum RestClientErrors {
HttpError = "HttpError",
HttpErrorM = "HttpErrorM",
}
export class HttpError extends AvaError {
public constructor(resp: RestResponse<HttpResponse>) {
super(RestClientErrors.HttpError, resp.Response.statusCode.toString());
this.response = resp;
}
public response: RestResponse<HttpResponse>;
}
export class HttpErrorM extends AvaError {
public constructor(resp: RestResponse<TNSHttpFormDataResponse>) {
super(RestClientErrors.HttpErrorM, resp.Response.statusCode.toString());
this.response = resp;
}
public response: RestResponse<TNSHttpFormDataResponse>;
}
The enum is useful when you need to catch the error, you can see this in the following example (same code of example page of template):
public async onRestRequestTap(args: EventData)
{
if (this._restItems.length < 5)
{
try
{
const client = new RestClient();
// resp is RestResponse type
const resp = await client.GET("https://jsonplaceholder.typicode.com/todos/" + (this._restItems.length + 1));
// result is StandardResponse<HomeItem> type
const result = resp.toStandardResponseData<HomeItem>();
Debugger.log(result);
this._restItems.push(result.data);
this.npc("restItems");
}
catch (error)
{
if (error.name === RestClientErrors.HttpError)
{
const e = error as HttpError;
Debugger.log(e.message);
dialogs.alert(e.message);
}
}
}
else
{
dialogs.alert("Items ended");
}
}
This code show how make a GET request with the RestClient.
The steps are simply, you create a client, you make the request (you can also pass headers).
After this, you have the RestResponse object and now you can use two functions:
Actually this RestClient implements a POST method from the Nativescript library and a POST MULTIPART FORM DATA by the library nativescript-http-formdata.
The RestResponse is of type T (TNSHttpFormDataResponse or HttpResponse) and you can access to response via response property.
When you use the toStandardResponse/Data method this is transparent.
Actually the template install by default the dewlinq and dewstrings libraries.