Skip to content

Realtime App With Angular and Firestore (AngularFire)

angularfire

In this tutorial, I'm going to show you how to use Firestore, which is a realtime database from Firebase with Angular. We are going to make use of the basic CRUD opeartions as well as AGM (Angular Google Maps).

Overview

This application will help you to have a realtime map with a data table showing your favorite places that you have visted or want to visit, so you can share this info with your friends. Screen Shot 2019-12-10 at 1.43.27 AM

Firebase setup

  1. Go to Firebase, Sign in with your google account
  2. Go to the console
  3. Click on Add project
  4. Give your project a name
  5. Disable __ Google Analytics for this project__
  6. Click on Create Project
  7. You will see a screen like this. Click on the web icon Screen Shot 2019-12-10 at 1.49.52 AM
  8. Register your app
  9. Copy the firebaseConfig object, and paste it somewhere secure, we will use it later on
  10. Click on Continue to Console
  11. Click on the big orange box that says Cloud Firestore
  12. Then, on the top, you will see a button that says Create database
  13. A modal will show up. Click on radio button__ Start in test mode__
  14. Click Next
  15. Select your Firestore location
  16. Click Done

Angular

Time to work on our Angular application.

  1. Create a new angular app

Note: Make sure to select SCSS as you CSS compiler.

ng new thisdot-tutorial
  1. Create a component for our form
ng g c places-form
  1. Create a component for our list of favorite places
ng g c places-list
  1. Create a component for our map with our favorite places
ng g c places-map
  1. Create a new service as follow:
ng g s places

Now, we are going to install all the packages needed, so we don't have to go back to our app.module file and deal with them later.

  1. Install firebase
npm i --save firebase @angular/fire
  1. Install Angular Google Maps (AGM)
npm i --save @agm/core
  1. Go to your app.module, and add the following modules as follows
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

/*External Modules */
import { ReactiveFormsModule } from '@angular/forms';
import { AngularFireModule } from '@angular/fire';
import { AngularFirestoreModule } from '@angular/fire/firestore';
import { AgmCoreModule } from '@agm/core';

/*App components */
import { AppComponent } from './app.component';
import { PlacesListComponent } from './places-list/places-list.component';
import { PlacesFormComponent } from './places-form/places-form.component';
import { PlacesMapComponent } from './places-map/places-map.component';

import { environment } from 'src/environments/environment';

@NgModule({
  declarations: [
    AppComponent,
    PlacesListComponent,
    PlacesFormComponent,
    PlacesMapComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule,
    AngularFireModule.initializeApp(environment.firebaseConfig),
    AngularFirestoreModule,
    AgmCoreModule.forRoot({
      apiKey: 'GOOGLE MAPS API KEY'
    })
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {}
  1. Generate a Google API Key Also, you'll need to make sure you have Maps JavaScript API service enabled in the Google Service Library

  2. Copy the API key, and paste it in the AgmCoreModule inside of the app.module.ts

  3. As you can see in the app.module.ts file, we have the following line:

AngularFireModule.initializeApp(environment.firebaseConfig),

This line is using the enviroment variable.

Go to the enviroment.ts file, and make sure your code looks like the following:

export const environment = {
  production: false,
  firebaseConfig: {
    apiKey: 'XXXXXXXX',
    authDomain: 'XXXXXXXX',
    databaseURL: 'XXXXXXXX',
    projectId: 'XXXXXXXX',
    storageBucket: 'XXXXXXXX',
    messagingSenderId: 'XXXXXXXX',
    appId: 'XXXXXXXX',
    measurementId: 'XXXXXXXX'
  }
}

Note: Copy/paste the credentials we copied from the firebase console earlier in the tutorial.

  1. Inside of your index.html file, add Semantic UI CDN to give some quick styling to our app
<link
  rel="stylesheet"
  href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css"
  integrity="sha256-9mbkOfVho3ZPXfM7W8sV2SndrGDuh7wuyLjtsWeTI1Q="
  crossorigin="anonymous"
/>
  1. Add the fontawesome CDN inside the index.html file

Get your own CDN link here

  1. Create a new model file with the name place.model.ts

  2. Copy/paste the following code inside of the place model

export interface Place {
  name: string;
  lat: number;
  long: number;
  id: string;
  visited: false;
}
  1. Go to your places-form.components.ts file. We are going to create our form as follows:

Note: I'm also injecting via dependency injection, my places service.

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { PlacesService } from '../places.service';

@Component({
 ....
})

export class PlacesFormComponent implements OnInit {
  form = new FormGroup({
    name: new FormControl(''),
    long: new FormControl(''),
    lat: new FormControl('')
  });
  constructor(private placesService: PlacesService) {}

  ngOnInit() {}

  onSubmit() {
    const place = this.form.value;
    this.placesService.addPlace({ ...place, visited: false });
    this.form.reset();
  }
}
  1. Inside of your places-form.component.html, copy/paste the code that contains the form.
 <div class="form-fav">
      <form class="ui form" [formGroup]="form" (ngSubmit)="onSubmit()">
        <div class="field">
             <input
             type="text"
             name="name"
             placeholder="Place Name"
             formControlName="name"
             />
       </div>
       <div class="field">
            <input
            type="text"
            name="latitude"
            placeholder="Latitude"
           formControlName="lat"
        />
      </div>
      <div class="field">
           <input
           type="text"
           name="Longitude"
           placeholder="Longitude"
           formControlName="long"
           />
      </div>
      <button class="ui inverted violet button" type="submit">
              <i class="fas fa-star"></i> Add to Favorites
      </button>
   </form>
</div>
  1. Now, go to your places-form.component.scss file, and add the following styling.
.form {
  display: flex;
  justify-content: space-evenly;
  border-bottom: 1px solid #222;
  padding: 8px;
  align-items: flex-start;
}
  1. Time to work with our places-list.component.ts file.

__Note: __In this file, we are going to create an Input() that will take the array of places comming from our parent component. Also, notice I'm importing the places service, because we are going to call some actions inside of our service like the delete and edit

import { Component, OnInit, Input } from '@angular/core';
import { PlacesService } from '../places.service';
import { Place } from '../place.model';

@Component({
  selector: 'app-places-list',
  templateUrl: './places-list.component.html',
  styleUrls: ['./places-list.component.scss']
})
export class PlacesListComponent implements OnInit {
  @Input() places: Place[];
  constructor(private placesService: PlacesService) {}

  ngOnInit() {}

  onDelete(id: string) {
    this.placesService.deletePlace(id);
  }

  onUpdate(id: string, visited: boolean) {
    this.placesService.updatePlace(id, visited);
  }
}
  1. In the places-list.component.html file, you are going create the list that displays your favorite places. The last column will be an actions column. This column will allow you to delete or edit your data.
<table class="ui celled table">
  <thead>
    <tr>
      <th><i class="fas fa-map-marker-alt"></i> Place Name</th>
      <th>Latitude</th>
      <th>Longitude</th>
      <th>Actions</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let p of places">
      <td>{{ p.name }}</td>
      <td>{{ p.lat }}</td>
      <td>{{ p.long }}</td>
      <td class="actions">
        <div class="action remove" (click)="onDelete(p.id)">
          Remove<i class="fas fa-backspace"></i>
        </div>
        <div class="action" (click)="onUpdate(p.id, p.visited)">
          <ng-container *ngIf="p.visited; else notVisited">
            <span class="visited"
              >Visited<i class="fas fa-smile-wink"></i
            ></span>
          </ng-container>
          <ng-template #notVisited>
            <span class="not-visited"
              >Not Visited <i class="fas fa-sad-tear"></i
            ></span>
          </ng-template>
        </div>
      </td>
    </tr>
  </tbody>
</table>
  1. Time to style our table :) Lets make it look at least a little decent. Inside of your places-list.component.scss file, add the following code.
.actions {
  display: flex;
  justify-content: space-around;
  .action {
    display: flex;
    align-items: center;
    cursor: pointer;

    i {
      margin: 2px;
    }
  }

  .remove {
    color: #ff3232;
  }

  .visited {
    color: rgb(32, 218, 141);
  }

  .not-visited {
    color: rgb(252, 214, 0);
  }
}
  1. Time to add the favorites places to our map. Open the places-map.component.ts file, and add the following code.

Note: As you can see, I'm creating an Input() that takes an array of places, which is the data that will populate our markers in the map.

import { Component, OnInit, Input } from '@angular/core';
import { Place } from '../place.model';

@Component({
  selector: 'app-places-map',
  templateUrl: './places-map.component.html',
  styleUrls: ['./places-map.component.scss']
})
export class PlacesMapComponent implements OnInit {
  @Input() places: Place[];

  //This will define the center of the map
  lat = 53.2734;
  long = -7.77832031;

  constructor() {}

  ngOnInit() {}
}
  1. Let's add the agm map component to our places-map.component.html file.

Note: I'm looping throught the array of places to add the markers to our map.

<div class="places">
  <div class="map">
    <agm-map [latitude]="lat" [longitude]="long" [zoom]="4">
      <agm-marker
        *ngFor="let p of places"
        [latitude]="p.lat"
        [longitude]="p.long"
      ></agm-marker>
    </agm-map>
  </div>
</div>
  1. Don't forget to specify the height of your map, or it won't appear. Specify the height inside of the places-map.component.scss file
agm-map {
  height: 100vh;
}
  1. Go to your app.component.ts file. We are going to create a variable that grabs the places stored in our Firestore databse.

Note: As you can see, I'm injecting the places services via dependency injection.

import { Component } from '@angular/core';
import { PlacesService } from './places.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'This Dot app';

  constructor(private placesService: PlacesService) {}
  places$ = this.placesService.places$;
}
  1. In this step, we are going to add all of our components to app.component.html for them to be displayed in our application.

Note: Notices I'm passing the places array to the inputs in the places-list, and places-map components. Also notice I'm using the async pipe to handle the subcription and unsubcription of the observable.

<div class="places">
  <app-places-form></app-places-form>
  <div class="columns" *ngIf="places$ | async as places">
    <div class="places-list">
      <app-places-list [places]="places"></app-places-list>
    </div>
    <div class="map">
      <app-places-map [places]="places"></app-places-map>
    </div>
  </div>
</div>
  1. Let's add the main styling to our app to give it the shell structure. Go your app.component.scss file, and add the following code:
.places {
  margin: 18px 18px;
  .columns {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    grid-template-areas: "places-list map";
  }

  .places-list {
    grid-area: places-list;
  }

  .map {
    grid-area: map;
  }
}

CRUD with Firestore

  1. Go to your places.service.ts, and add the following code:

Note: the places$ field is an observable from firestore, and delete, add, update actions are promises.

import { Injectable } from '@angular/core';
import { AngularFirestore } from '@angular/fire/firestore';
import { Place } from './place.model';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class PlacesService {
  constructor(private firestore: AngularFirestore) {}

  firestorePlacesCollection = this.firestore.collection('places');

  //READ
  places$ = this.firestorePlacesCollection.snapshotChanges().pipe(
    map(actions => {
      return actions.map(p => {
        const place = p.payload.doc;
        const id = place.id;
        return { id, ...place.data() } as Place;
      });
    })
  );

  //CREATE
  async addPlace(data: Place): Promise<void> {
    try {
      await this.firestorePlacesCollection.add(data);
    } catch (err) {
      console.log(err);
    }
  }

  //UPDATE
  async updatePlace(id: string, visited: boolean): Promise<void> {
    try {
      await this.firestorePlacesCollection
        .doc(id)
        .set({ visited: !visited }, { merge: true });
    } catch (err) {
      console.log(err);
    }
  }

  //DELETE
  async deletePlace(id: string): Promise<void> {
    try {
      await this.firestorePlacesCollection.doc(id).delete();
    } catch (err) {
      console.log(err);
    }
  }
}

Time to run our app!

  1. In your command line, run
ng serve --o

Note: You might see an error message telling you that something went wrong with the Google Maps API. In this case, you most probably need to enable billing for this project in your Google Cloud Account. No worries, you won't be charged since there is a free tier available.

  1. Create some data, and you should see something like this: Screen Shot 2019-12-10 at 1.43.27 AM

  2. Go back to the Firebase console, and see the Firestore console with the new data created.

Don't forget to follow me on Twitter @devpato, and let me know what you think about this tutorial!

This Dot Labs is a development consultancy that is trusted by top industry companies, including Stripe, Xero, Wikimedia, Docusign, and Twilio. This Dot takes a hands-on approach by providing tailored development strategies to help you approach your most pressing challenges with clarity and confidence. Whether it's bridging the gap between business and technology or modernizing legacy systems, you’ll find a breadth of experience and knowledge you need. Check out how This Dot Labs can empower your tech journey.

You might also like

You Don't Need NgRx To Write a Good Angular App cover image

You Don't Need NgRx To Write a Good Angular App

NgRx is a great tool that allows you to manage state and side effects in Angular applications in a Redux-like manner. It streamlines state changes with its unidirectional data flow, and offers a structured approach to handling data and side effects. Numerous posts on our blog detail its strengths and affiliated techniques. Some Angular developers even argue that incorporating NgRx is imperative once an app expands beyond two features. While NgRx can undoubtedly enhance an Angular application or library by simplifying debugging, translating business logic into code, and improving the architecture, it does present a steep learning curve. Despite the provocative title, there is some truth to the statement: your app or library may indeed not need NgRx. Surprisingly, I successfully developed a suite of enterprise Angular libraries over five years without involving NgRx. In that project, we decided to opt out of using a state management library like NgRx because of its steep learning curve. Developers with varying levels of Angular expertise were involved, and the goal was to simplify their experience. My bold assertion is that, with careful consideration of architectural patterns, it is entirely possible to develop a robust app or library using only Angular, without any third-party libraries. Employing select design patterns and leveraging Angular's built-in tools can yield a highly maintainable app, even without a dedicated state management library. Having shared my somewhat audacious opinion, let me now support it by outlining a few patterns that facilitate the development of a maintainable, stateful Angular application or library without NgRx. Services and the Singleton Pattern Services provided in root` or a module yield a shared instance across the entire app or module, effectively rendering them singletons. This characteristic makes them ideal for managing and sharing state across components without requiring a dedicated state management tool like NgRx. Particularly, for small to medium-sized applications, a "state service" can be a straightforward and effective alternative to a comprehensive state management solution when implemented correctly. To accurately implement state in a singleton service, consider the following: - Restrict state data to private properties and expose them only through public methods or observables to prevent external mutations. Such a pattern safeguards the integrity of your state by averting unauthorized modifications. - Utilize BehaviorSubjects or signals to enable components to respond to state changes. Both BehaviorSubject` and `SettableSignal` retain the current value and emit it to new subscribers immediately. Components can then subscribe to these to receive the current value and any subsequent updates. - Expose public methods in your service that manage state modifications to centralize the logic for updating the state and incorporate validation, logging, or other necessary side effects. - When modifying state, always return a new instance of the data rather than altering the original data. This ensures that references are broken and components that rely on change detection can accurately detect changes. Good Component Architecture Distinguish your UI components into stateful (containers) and stateless (presentational) components. Stateful components manage data and logic, while stateless components merely receive data via inputs and emit events without maintaining an internal state. Do not get dragged into the rabbit hole of anti-patterns such as input drilling or event bubbling while trying to make as many components presentational as possible. Instead, use a Data Service Layer to provide a clean abstraction over backend API calls and handle error management, data transformation, caching, and even state management where it makes sense. Although injecting a service into a component technically categorizes it as a "smart" component, segregating the data access logic into a separate service layer ultimately enhances modularity, maintainability, scalability, and testability. Immutability A best practice is to always treat your state as immutable. Instead of modifying an object or an array directly, you should create a new copy with the changes. Adhering to immutability ensures predictability and can help in tracking changes. Applying the ChangeDetectionStrategy.OnPush strategy to components whenever possible is also a good idea as it not only optimizes performance since Angular only evaluates the component for changes when its inputs change or when a bound event is triggered, but it also enforces immutability. Change detection is only activated when a different object instance is passed to the input. Leveraging Angular Router Angular's router is a powerful tool for managing application state. It enables navigation between different parts of an application, allowing parameters to be passed along, effectively using the URL as a single source of truth for your application state, which makes the application more predictable, bookmarkable, and capable of maintaining state across reloads. Moreover, components can subscribe to URL changes and react accordingly. You can also employ router resolvers to fetch data before navigating to a route, ensuring that the necessary state is loaded before the route is activated. However, think carefully about what state you store in the URL; it should ideally only contain the state essential for navigating to a specific view of your application. More ephemeral states, like UI state, should be managed in components or services. Conclusion Angular provides lots of built-in tools and features you can effectively leverage to develop robust, maintainable applications without third-party state management libraries like NgRx. While NgRx is undoubtedly a valuable tool for managing state and side effects in large, complex applications, it may not be necessary for all projects. By employing thoughtful design patterns, such as the Singleton Pattern, adhering to principles of immutability, and leveraging Angular's built-in tools like the Router and Services, you can build a highly maintainable and stateful Angular application or library....

A Guide to Custom Angular Attribute Directives cover image

A Guide to Custom Angular Attribute Directives

When working inside of Angular applications you may have noticed special attributes such as NgClass, NgStyle and NgModel. These are special attributes that you can add to elements and components that are known as attribute directives. In this article, I will cover how these attributes are created and show a couple of examples. What are Attribute Directives? Angular directives are special constructs that allow modification of HTML elements and components. Attribute directives are also applied through attributes, hence the name. There exist other types of directives such as structural directives as well, but we’re just going to focus on attribute directives. If you’ve used Angular before then you have almost certainly used a couple of the attribute directives I mentioned earlier before. You are not limited to just the built-in directives though. Angular allows you to create your own! Creating Attribute Directives Directives can be created using code generation via the ng CLI tool. ` ng generate directive ` This will create a file to house your directive and also an accompanying test file as well. The contents of the directive are very barebones to start with. Let’s take a look. ` import { Directive } from '@angular/core'; @Directive({ selector: '[appExample]', }) export class ExampleDirective { constructor() {} } ` You will see here that directives are created using a @Directive decorator. The selector in this case is the name of the attribute as it is intended to be used in your templates. The square brackets around the name make it an attribute selector, which is what we want for a custom attribute directive. I would also recommend that a prefix is always used for directive names to minimize the risk of conflicts. It should also go without saying to avoid using the ng prefix for custom directives to avoid confusion. Now, let’s go over the lifecycle of a directive. The constructor is called with a reference to the ElementRef that the directive was bound to. You can do any initialization here if needed. This element reference is dependency injected, and will be available outside the constructor as well. You can also set up @HostListener handlers if you need to add functionality that runs in response to user interaction with the element or component, and @Input properties if you need to pass data to the directive. Click Away Directive One useful directive that doesn’t come standard is a click away directive. This is one that I have used before in my projects, and is very easy to understand. This directive uses host listeners to listen for user input, and determine whether the element that directive is attached to should be visible or not after the click event occurs. ` @Directive({ selector: '[appClickAway]', }) export class ClickAwayDirective { @Output() onClickAway: EventEmitter = new EventEmitter(); constructor(private elementRef: ElementRef) {} @HostListener('document:click', ['$event']) onClick(event: PointerEvent): void { if (!this.elementRef.nativeElement.contains(event.target)) { this.onClickAway.emit(event); } } } ` There are a few new things in this directive we’ll briefly go over. The first thing is the event emitter output onClickAway. A generic directive isn’t going to know how to handle click away behavior by itself as this will change based on your use case when using the directive. To solve this issue, we make the directive emit an event that the user of the directive can listen for. The other part is the click handler. We use @HostListener to attach a click handler so we can run our click away logic whenever clicks are done. The one interesting thing about this directive is that it listens to all click events since we’ve specified ‘document’ in the first parameter. The reason for this is because we care about listening for clicking anything that isn’t the element or component that the directive is attached to. If we didn’t do this, then the event handler would only fire when clicking on the component the directive is attached to, which defeats the purpose of a click away handler. Once we’ve determined the element was not clicked, we emit the aforementioned event. Using this directive makes it trivial to implement click away functionality for both modals and context menus alike. If we have a custom dialog component we could hook it up like this: ` Dialog Box This is a paragraph with content! ` If you want to see this directive in action, then you can find it in our blog demos repo here. Drag and Drop Directive Another useful directive is one that assists with drag and drop operations. The following directive makes elements draggable, and executes a function with a reference to the location where the element was dragged to. ` @Directive({ selector: '[appDragDrop]', }) export class DragDropDirective implements OnInit, OnDestroy { @Output() onDragDrop: EventEmitter = new EventEmitter(); mouseDown$ = new Subject(); mouseUp$ = new Subject(); destroy$ = new Subject(); constructor(private elementRef: ElementRef) {} ngOnInit(): void { this.mouseDown$ .pipe(takeUntil(this.destroy$)) .pipe(exhaustMap(() => this.mouseUp$.pipe(take(1)))) .subscribe((event) => { if ( event.target && event.target instanceof Element && !this.elementRef.nativeElement.contains(event.target) ) { this.onDragDrop.emit(event); } }); } ngOnDestroy(): void { this.destroy$.next(null); this.destroy$.complete(); } @HostListener('mousedown', ['$event']) onMouseDown(event: MouseEvent): void { this.mouseDown$.next(event); } @HostListener('document:mouseup', ['$event']) onMouseUp(event: MouseEvent): void { this.mouseUp$.next(event); } } ` Just like the previous directive example an event emitter is used so the user of the directive can associate custom functionality with it. RxJs is also utilized for the drag and drop detection. This directive uses the exhaustMap function to create an observable that emits both after a mouse down, and finally a mouse up is done. With that observable, we can subscribe to it and call the drag and drop callback so long as the element that’s dragged on isn’t the component itself. Note how the mouse down event is local to the component while the mouse up event is attached to the document. For mouse down, this is done since we only want the start of the dragging to be initiated from clicking the component itself. The mouse up must listen to the document since the dragging has to end on something that isn’t the component that we’re dragging. Just like the previous directive, we simply need to reference the attribute and register an event handler. ` Drag me over something! ` Conclusion In this article, we have learned how to write our own custom attribute directives and demonstrated a couple of practical examples of directives you might use or encounter in the real world. I hope you found this introduction to directives useful, and that it helps you with writing your own directives in the future! You can find the examples shown here in our blog demos repository if you want to use them yourself....

Closer look at the DNA of the OpenFin Platform API cover image

Closer look at the DNA of the OpenFin Platform API

This blog takes a deep dive into the newly launched Platform API by OpenFin. After only a few days of playing around with the software, I realized just how much capacity it has and how many good features there are for developers. Ultimately, this robust set of features will significantly enhance your user’s experience. Before I begin, let's start with a bit of background information on OpenFin! Dictionary** API__: Application Programming Interface OS__: Operating System CSS__: Cascading Style Sheets What is OpenFin? Openfin is a tech company focused on modernizing desktops, and accelerating innovation in the financial sector. Simply put, OpenFin is the Operating System of Finance! With this, you get the power you need, the freedom you want, and the security you must have. The Problem If you are in the financial sector, you know that it is very important to be able to visualize everything on one screen when interacting with multiple applications. We usually tend to arrange windows over and over, but that takes time, and our applications do not work with each other, nor do they share all data between them by nature. Most importantly, we have to try to make sure all of these apps are secure! The Solution Platform API of course! What is the OpenFin Platform API? It's a software that will help you build desktop platforms at the speed of light. The Platform API will also facilitate the work of creating a merged user experience across the multiple applications. > “The Platform API is for central architecture teams who want to provide web apps with a unified desktop experience and common look & feel.” - OpenFin Engineer Key Features of the OpenFin Platform API - Layout management (e.g. window drag-and-drop and tabbing) - Customization of window look & feel - Styling via CSS - URL for loading the title bar window - Customization of all Platform APIs (behaviors) - Save and restore your window view. - Window level context (different from FDC3) - “Smart” Manifests to describe platforms via a single .JSON file The Powerful Gridlayout One of my favorite features is their grid layout. This feature has helped me reduce the amount of time it takes to develop an app. It can get pretty complicated to create dynamic grids that work with internal and external windows, by dragging and dropping. Now, if you see it from the end user point of view, this is an awesome idea, because the grid is customizable! Now, I know what you are thinking. And no, you don't need to ask the developer to change the layout of the application. You, as an end user, can change the layout as well. This gives every end user the opportunity to have customs views of their apps that best fit their needs, and grow their productivity. As a developer, I believe this is a huge benefit, since I don't have to worry about writing the code for this dynamic grid, nor do I need to worry about customizing the layout for each end user or client, which allows me to focus on the actual applications that will be used inside of the Platform API. Because a Grid layout is not enough The Platform API gives you the ability to power up your platform not only with custom layouts, but also with tabs! As a developer, I can develop my applications used inside of the platform with the assurance that they can be grouped together on tabs. And one of the coolest things is that you can customize them! If you are an end user of the platform, there are so many benefits here. E.g You can group the tabs by colors, where each color represents windows that belong to a certain group. This is huge. I have seen monitors of people working in the financial sector with 20 open windows and sometimes, users get lost in this. It's hard to manage what's going on. Your perfect setup...always So while working with the Platform API, I found out that you can save the current platform setup. This is an amazing feature. When working with dynamic layout, having to re-arrange things every time the code compiles can become very tedious. Now, imagine the benefits of this for the end users! As a developer, you can easily retrieve the existing snapshot of your saved platform by using the applySnapshot__ method. `javascript platform.applySnapshot(mySnapshot, {closeExistingWindows: true}); ` Thanks to this, you don't have to worry about losing the perfect setup that took you time to arrange. The setup will be always the same as long as you want to apply the saved snapshot! Advanced workflows The Platform API allows you to get the current context of your window. Thanks to this, you can easily save it into the platform's snapshots to re-use the context when the aplySnapShot** method is called. The Core Now, let's take a closer look at the core of OpenFin’s Platform API and dive into some code examples. What is the core? It’s the manifest! I like to refer to it as the core because it is what carries all the information which constructs your Platform API project. The manifest is located inside of a .json** file AKA the **app.json** Let’s Get Started Let's create our manifest: `javascript { "platform": { "uuid": "thisdotplatform" } } ` As you can see, this is the beginning of a new project using the Platform API. All you have to do is declare the "platform" object in your app.json. Now let’s dive into the features to customize the application experience. Customizing the Platform API Window Customize your window's look and feel using css, and by adding defaultWindowOptions**. You manifest will look as follows: `javascript { "platform": { "uuid": "exampleplatform", "defaultWindowOptions": { "stylesheetUrl": "url-to-css-stylesheet" } } } ` Take a look at this file to see what css selectors are available in the Platform API. You can also replace the default windows that come with the Platform API. To do this, specify the url__ property as a window option in your manifest. You can import your custom HTML as follows: `javascript ... "defaultWindowOptions": { "url": "url-to-html-file" } ... ` When working with your custom window, all you have to do is consider the following: > This HTML file must specify a div component with the ID layout-container where you want the layout to be rendered. This will ensure that the window has a target to render the layout in. A url can also be specified in windowOptions__ in a snapshot, or when launching a snapshot via other methods. Window Commands OpenFin enables your Platform API application to work and feel like a native desktop application. That's why Openfin engineers further enhanced this experience by adding commands (with appropriate hotkeys) to help improve user experience. These commands can be added to the platform object inside of your Platform API manifest. `javascript ... "commands": [ { "command": "stack.nextTab", "keys": "Ctrl+T" } ] ... ` Window Snapshot Another important property of the manifest is the snapshot**. This property defines the structure of your window inside of the Platform. The snapshot needs to contain the window property where we will define the objects that go inside of it like *views*, and you can even define the structure of the *grid* by the layout property each window has. One cool feature about windows is that they can be created and destroyed by the end user, or developer, at any time. `javascript { ... "snapshot": { "windows": [ { "defaultWidth": 800, "defaultHeight": 600, "layout": { // the structure of your grid } } ] } ... } ` Window Layout This property defines the structure of your window. The layout works on a grid system. When working with the layouts, you have to add the content property inside of the layouts property. This content property contains an inner property called type**. The values inside of the type value are the following: - row - column - stack - component In the following code snippet, you can see how I'm using the the content property with the value stack** as my **type** value. Another thing to notice is that there's content inside of other content. The Platform API allows us to have nested content to have the ability to give our window the structure we want. `javascript ... "layout": { "content": [ { "type": "stack", "content": [ { "type": "component" } ] } ] } ... ` View ComponentState Finally, another property that is worth mentioning is the componentState**. This property gives us the option to provide more information about our view. Let's take a look at the following example. `javascript ... "componentState": { "name": "examplelabs_view", "url": "https://www.thisdot.co/" } ... ` This view will render the website of https://www.thisdot.co** inside of the view. Take a look to this complete example: `javascript { "snapshot": { "windows": [ { "defaultWidth": 600, "defaultHeight": 600, "layout": { "content": [ { "type": "stack", "content": [ { "type": "component", "componentName": "view", "componentState": { "name": "componentA1", "processAffinity": "ps1", "url": "https://www.example.com" } }, { "type": "component", "componentName": "view", "componentState": { "name": "componentA2", "url": "https://cdn.openfin.co/embed-web/chart.html" } } ] } ] } } ] } } ` If you want to learn more about the manifest and the Platform API, take a look at the official resources: - https://developers.openfin.co/docs/platform-api - https://cdn.openfin.co/docs/javascript/canary/View.html#options - https://cdn.openfin.co/docs/javascript/canary/Window.html#options - https://cdn.openfin.co/docs/javascript/stable/Platform.html Conclusion Working with Platform API has so many wonderful benefits. It gives me the opportunity to create more flexible software with consistent design, better user experience, and greater security. The Platform API has helped me deliver products faster, with better quality, without compromising the security of my software. OpenFin is changing the way we interact with financial software. Don’t miss your chance to use it!...

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights) cover image

I Broke My Hand So You Don't Have To (First-Hand Accessibility Insights)

We take accessibility quite seriously here at This Dot because we know it's important. Still, throughout my career, I've seen many projects where accessibility was brushed aside for reasons like "our users don't really use keyboard shortcuts" or "we need to ship fast; we can add accessibility later." The truth is, that "later" often means "never." And it turns out, anyone could break their hand, like I did. I broke my dominant hand and spent four weeks in a cast, effectively rendering it useless and forcing me to work left-handed. I must thus apologize for the misleading title; this post should more accurately be dubbed "second-hand" accessibility insights. The Perspective of a Developer Firstly, it's not the end of the world. I adapted quickly to my temporary disability, which was, for the most part, a minor inconvenience. I had to type with one hand, obviously slower than my usual pace, but isn't a significant part of a software engineer's work focused on thinking? Here's what I did and learned: - I moved my mouse to the left and started using it with my left hand. I adapted quickly, but the experience wasn't as smooth as using my right hand. I could perform most tasks, but I needed to be more careful and precise. - Many actions require holding a key while pressing a mouse button (e.g., visiting links from the IDE), which is hard to do with one hand. - This led me to explore trackpad options. Apart from the Apple Magic Trackpad, choices were limited. As a Windows user (I know, sorry), that wasn't an option for me. I settled for a cheap trackpad from Amazon. A lot of tasks became easier; however, the trackpad eventually malfunctioned, sending me back to the mouse. - I don't know a lot of IDE shortcuts. I realized how much I've been relying on a mouse for my work, subconsciously refusing to learn new keyboard shortcuts (I'll be returning my senior engineer license shortly). So I learned a few new ones, which is good, I guess. - Some keyboard shortcuts are hard to press with one hand. If you find yourself in a similar situation, you may need to remap some of them. - Copilot became my best friend, saving me from a lot of slow typing, although I did have to correct and rewrite many of its suggestions. The Perspective of a User As a developer, I was able to get by and figure things out to be able to work effectively. As a user, however, I got to experience the other side of the coin and really feel the accessibility (or lack thereof) on the web. Here are a few insights I gained: - A lot of websites apparently tried_ to implement keyboard navigation, but failed miserably. For example, a big e-commerce website I tried to use to shop for the aforementioned trackpad seemed to work fine with keyboard navigation at first, but once I focused on the search field, I found myself unable to tab out from it. When you make the effort to implement keyboard navigation, please make sure it works properly and it doesn't get broken with new changes. I wholeheartedly recommend having e2e tests (e.g. with Playwright) that verify the keyboard navigation works as expected. - A few websites and web apps I tried to use were completely unusable with the keyboard and were designed to be used with a mouse only. - Some sites had elaborate keyboard navigation, with custom keyboard shortcuts for different functionality. That took some time to figure out, and I reckon it's not as intuitive as the designers thought it would be. Once a user learns the shortcuts, however, it could make their life easier, I suppose. - A lot of interactive elements are much smaller than they should be, making it hard to accurately click on them with your weaker hand. Designers, I beg you, please make your buttons bigger. I once worked on an application that had a "gloves mode" for environments where the operators would be using gloves, and I feel like maybe the size we went with for the "gloves mode" should be the standard everywhere, especially as screens get bigger and bigger. - Misclicking is easy, especially using your weaker hand. Be it a mouse click or just hitting an Enter key on accident. Kudos to all the developers who thought about this and implemented a confirmation dialog or other safety measures to prevent users from accidentally deleting or posting something. I've however encountered a few apps that didn't have any of these, and those made me a bit anxious, to be honest. If this is something you haven't thought about when developing an app, please start doing so, you might save someone a lot of trouble. Some Second-Hand Insights I was only a little bit impaired by being temporarily one-handed and it was honestly a big pain. In this post, I've focused on my anecdotal experience as a developer and a user, covering mostly keyboard navigation and mouse usage. I can only imagine how frustrating it must be for visually impaired users, or users with other disabilities, to use the web. I must confess I haven't always been treating accessibility as a priority, but I've certainly learned my lesson. I will try to make sure all the apps I work on are accessible and inclusive, and I will try to test not only the keyboard navigation, ARIA attributes, and other accessibility features, but also the overall experience of using the app with a screen reader. I hope this post will at least plant a little seed in your head that makes you think about what it feels like to be disabled and what would the experience of a disabled person be like using the app you're working on. Conclusion: The Humbling Realities of Accessibility The past few weeks have been an eye-opening journey for me into the world of accessibility, exposing its importance not just in theory but in palpable, daily experiences. My short-term impairment allowed me to peek into a life where simple tasks aren't so simple, and convenient shortcuts are a maze of complications. It has been a humbling experience, but also an illuminating one. As developers and designers, we often get caught in the rush to innovate and to ship, leaving behind essential elements that make technology inclusive and humane. While my temporary disability was an inconvenience, it's permanent for many others. A broken hand made me realize how broken our approach towards accessibility often is. The key takeaway here isn't just a list of accessibility tips; it's an earnest appeal to empathize with your end-users. "Designing for all" is not a checkbox to tick off before a product launch; it's an ongoing commitment to the understanding that everyone interacts with technology differently. When being empathetic and sincerely thinking about accessibility, you never know whose life you could be making easier. After all, disability isn't a special condition; it's a part of the human condition. And if you still think "Our users don't really use keyboard shortcuts" or "We can add accessibility later," remember that you're not just failing a compliance checklist, you're failing real people....