Quantcast
Channel: DEV Community: David Dal Busco
Viewing all 124 articles
Browse latest View live

Get App Name And Version In Angular

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-height days left until hopefully better days.

Today, Sunday, I had to upgrade an application I’ve developed last year with Ionic and Angular because my client seems to finally have decided to publish it in stores (better start without technical debt 😉).

I’ve to say, the update process to Angular v9 and Ionic v5 went just smoothly. It took me a bit more time to upgrade the Cordova plugins, specially the one dedicated to Firebase, but fortunately I found the solution shared by his maintainer, Dave Alden, who always does an outstanding job.

Once done, I was still facing one last issue. I didn’t touched the app in months and meanwhile, Google Analytics had been really deprecated and I was not able anymore to build my platform because the corresponding plugin was outdated.

Instead of finding the solution, I just removed my old GA implementation and migrated to the new Firebase Analytics implementation in less time than expected.

For the web, I’m using AngularFire, which offers an option to track deployments with application’s name and versions.

Therefore here is how you can get or read the name and version of your application in Angular.

Import JSON File In TypeScript

We don’t want to duplicate the app’s name or version, that’s why I suggest that we read these from our package.json . In order to import JSON files in TypeScript, we need to instruct the compiler to accept such type of data. To do so, in our tsonfig.json we turn the option resolveJsonModule to true .

"compilerOptions":{"resolveJsonModule":true},

Environment Variables

A convenient way to handle environment variables is possible, out of the box, in Angular through the use of the multiples environment.ts files. That’s why we enhance these to “inject” our application’s name and version.

import{name,version}from'../../package.json';exportconstenvironment={production:true,name,version};

That’s it, in our code we have now access the information 😁.

AngularFire Analytics

If like me, you use AngularFire and would like to track these information, proceed as the following in your app.module.ts . Note that in the spinet I also set anonymize_ip to true , as it should, in my humble opinion, always be the case.

import{AngularFireAnalyticsModule,APP_NAME,APP_VERSION,CONFIG}from'@angular/fire/analytics';import{environment}from'../environments/environment';@NgModule({providers:[{provide:CONFIG,useValue:{allow_ad_personalization_signals:false,anonymize_ip:true}},{provide:APP_NAME,useValue:environment.name},{provide:APP_VERSION,useValue:environment.version}]})exportclassAppModule{}

Summary

I’m agree, this isn’t the deepest and longest blog post I have ever written but I hope it might be useful anyway to someone in the future, one never knows 😄.

Stay home, stay safe!

David

Cover photo by Joe Chau on Unsplash


Deploy Apps And Functions To Firebase From A Mono Repo With GitHub Actions

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-seven days left until hopefully better days.

I am a big fan of a blog post published by Julien Renaux a couple of months ago in which he displays how to deploy an application using GitHub Actions to Firebase Hosting.

The article is super comprehensive and I even already have published a following post about it once before 🤣. Nevertheless I think that this challenge is the perfect excuse to publish again another follow-up 😉.

Mono Repo

Our open source project DeckDeckGo contains many Progressive Web Apps and Cloud Functions, for which, obviously, I did set up GitHub actions as Julien displayed.

In this blog post I share the enhancement I had to implement in order to make the integration supports a mono repo.

To Firebase Hosting

Let’s say that one of your application is available in a sub-folder of our repo called docs . Basically, everything you have to do in addition to the original post is to prefix all steps of the Action with your sub-directory, respectively with docs .

Concretely, if for example we would like to trigger the action when a merge happens in the master branch, but, only if a modifications impacts our specific app, we specify a path to the Action on listener.

Listen to push to master for the all repo:

on:push:branches:-master

Listen to push to master only if sub-folder is affected:

on:push:branches:-masterpaths:-'docs/**'

Because the Git checkout of our pipeline happens on the root level of our repo, when it goes to installing dependencies or running a build, we do have to observe our sub-folder too. For such purpose, GitHub Action provides a handy option working-directory for the npm steps.

Run npm in the root folder:

-name:Install Dependenciesrun:npm ci

Run npm in a sub-directory:

-name:Install Dependenciesrun:npm ciworking-directory:./docs

Likewise, when it goes to artifacts, we also have to prefix the path to our bundle.

Archiving without sub-folder:

-name:Archive Artifactuses:actions/upload-artifact@masterwith:name:wwwpath:www

Archiving with sub-folder:

-name:Archive Artifactuses:actions/upload-artifact@masterwith:name:docspath:docs/www

Finally, the Firebase Action does also provide an option to specify a project path.

Deploy to Firebase from root:

-name:Deploy to Firebaseuses:w9jds/firebase-action@masterwith:args:deploy --only hostingenv:FIREBASE_TOKEN:${{ secrets.FIREBASE_TOKEN }}

Deploy to Firebase for a sub-directory:

-name:Deploy to Firebaseuses:w9jds/firebase-action@masterwith:args:deploy --only hostingenv:FIREBASE_TOKEN:${{ secrets.FIREBASE_TOKEN }}PROJECT_ID:"default"PROJECT_PATH:"./docs"

All in all, here’s the action to deploy our application for a sub-folder of our
mono repo to Firebase Hosting:

name:CI - Docson:push:branches:-masterpaths:-'docs/**'jobs:build:name:Buildruns-on:ubuntu-lateststeps:-name:Checkout Repouses:actions/checkout@master-name:Install Dependenciesrun:npm ciworking-directory:./docs-name:Buildrun:npm run buildworking-directory:./docs-name:Archive Artifactuses:actions/upload-artifact@masterwith:name:docspath:docs/wwwdeploy:name:Deployneeds:buildruns-on:ubuntu-lateststeps:-name:Checkout Repouses:actions/checkout@master-name:Download Artifactuses:actions/download-artifact@masterwith:name:docspath:docs/www-name:Deploy to Firebaseuses:w9jds/firebase-action@masterwith:args:deploy --only hostingenv:FIREBASE_TOKEN:${{ secrets.FIREBASE_TOKEN }}PROJECT_ID:"default"PROJECT_PATH:"./docs"

To Firebase Cloud

In DeckDeckGo we are also taking advantages of the amazing Cloud Functions features of Firebase.

The process is basically the same as previously with that difference that you don’t have to build or even bundle anything because these steps are contained in the deployment process it self. Therefore checking out the repo, installing the dependencies and deploying is going to do the trick.

Note that again, as we have a mono repo, the following example happens in a sub-directory called cloud which contains the functions.

name:CI - Cloudon:push:branches:-masterpaths:-'cloud/**'jobs:deploy:name:Deployruns-on:ubuntu-lateststeps:-name:Checkout Repouses:actions/checkout@master-name:Install Dependenciesrun:npm ciworking-directory:./cloud/functions-name:Deploy to Firebaseuses:w9jds/firebase-action@masterwith:args:deploy --only functionsenv:FIREBASE_TOKEN:${{ secrets.FIREBASE_TOKEN }}PROJECT_ID:"default"PROJECT_PATH:"./cloud"

Summary

GitHub actions are awesome! Seriously, few lines, few configuration, supports mono repo, really a perfect solution for a project like ours.

That being said, notably if you are and want to use them in your company, you might be interested by another blog post published by Julien which describes some possible risks linked to versioning and dependencies.

You are also most welcomed to have a look to our open source Actions in our repo or even better, if you notice anything which can be improved in DeckDeckGo, don’t hesitate to open an issue or even better, send us a Pull Request 😃.

Stay home, stay safe!

David

Cover photo by 张 嘴 on Unsplash

Starting In A New Company? Think Npmrc And Git Name

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-six days left until hopefully better days.

Every single time I am hired to join a team as an external developer for a while, I can guarantee you that I will have to google how I can configure the company npm Enterprise registry on my laptop 🙈. Moreover, it is also pretty sure that my first commit is going to happens with my GitHub username peterpeterparker instead of my real name or any other expected IDs 😄.

Therefore see this new article as an open letter to my future self 😉.

Setting your company’s npm Enterprise registry

If the company has hired you, there is a good chance that, unfortunately, at least some of their work is only available as closed source and distributed with a npm private registry. To configure such an entry, you can run the following command line in a terminal.

npm config set registry

Npmrc

If like me you are using your own laptop, you might not want to modify or mix up your private configuration with you client configuration right?

That’s why npmrc is there for us. With its help we are going to be able to define and switch between multiple npm profiles.

If you have not yet installed it, let’s first do so.

npm i npmrc -g

Once installed, we can use it to create Enterprise profile. For example, let’s create a profile called “client”.

npmrc -c client

After the profile has been created, it becomes the one in use, therefore, if we run the registry command, it will now set the registry only for the active profile.

npm config set registry 

Switch Between Profiles

Cool we now in our “client” profile, but how do we go back to the previous one, the default one? Nothing easier, run the npmrc command followed by the profile’s name.

npmrc default

Or if you want to switch back to the “client” one.

npmrc client

If you are lost and don’t know which profile is currently active, typing npmrc without any arguments will list the available profiles.

npmrc

Available npmrcs:

* default

client

Manual Configuration

You might like command lines but you may also rather like to modify manually your configuration using vi for example 😉.

On a Mac, the profiles find place in a folder .npmrcs added to your user directory. When you switch between these, npmrc is going to change the symlink at the root of your profile.

ls /Users/daviddalbusco/.npmrcs/default
/Users/daviddalbusco/.npmrcs/default

ls /Users/daviddalbusco/.npmrcs/client
/Users/daviddalbusco/.npmrcs/client

ls-ltr /Users/daviddalbusco/.npmrc
/Users/daviddalbusco/.npmrc -> /Users/daviddalbusco/.npmrcs/default

Setting Your Name In Git

To specify another name and email for your Git activity, you can either edit your global config or proceed by repository, up to you. I personally rather like to proceed by projects because I’ve got many clients.

Therefore, if like me you want to use your real name instead of your super cool GitHub username and also specify the email your client assigned to you, you can edit the file .git/config which find places, normally, in any Git project. In this data, add the following information:

[user]
    email = david.dalbusco@company.com
    name = David Dal Busco

And that’s it, all the interactions with the specific repo are going to be identified with these information.

Note that if you rather like to specify other inputs on a global level, you can run the following command lines.

git config --global user.name "David Dal Busco"
git config --global user.email "david.dalbusco@company.com"

Finally, in case you would have already performed a commit and are looking to amend your last commit user name, you can proceed with the --amend option of Git.

git commit --amend--author=”David Dal Busco <david.dalbusco@company.com>”

Summary

Well I am pretty sure that even if I wrote the above lines, I may forget again in the future how I have to proceed. But at least this time, I will just have to browse my blog history 😇.

Stay home, stay safe!

David

Cover photo by Max Rovensky on Unsplash

Test Angular Pipes With Services

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-five days left until hopefully better days.

Today I spent much time deep focused at writing new Angular components and their related unit tests, that I even missed this morning online “stand-up” and almost feel like I spend my day in some kind of vortex.

Anyway, I like this challenge, I don’t want to skip today’s blog post and I would like to share with you how I tested a new pipe I created. Moreover, I don’t pretend to be the champion of the exercise, therefore, if you notice anything which can be enhanced, ping me with your comments, I would be happy to improve my skills 🙏.

Create A Pipe

Let’s first create a blank pipe called “filter” with the ng command line.

ng g pipe filter

This creates a blank pipe like the following:

import{Pipe,PipeTransform}from'@angular/core';@Pipe({name:'filter'})exportclassFilterPipeimplementsPipeTransform{transform(value:any,...args:any[]):any{returnnull;}}

And it’s related test:

import{FilterPipe}from'./filter.pipe';describe('FilterPipe',()=>{it('create an instance',()=>{constpipe=newFilterPipe();expect(pipe).toBeTruthy();});});

You can be or not an Angular fan but I think we can all be agree that it’s pretty cool to have a CLI which creates class and related test without any effort.

Create A Service

As staten in my opening, the goal is to test a pipe which uses an injected service.

ng g service translation

For demo purpose, we create this dummy service “translation” wich return not that much except either “Génial” or “Awesome” as an observable.

import{Injectable}from'@angular/core';import{Observable,of}from'rxjs';@Injectable({providedIn:'root'})exportclassTranslationService{translate(lang:string):Observable<string>{returnof(lang==='fr'?'Génial':'Awesome');}}

Implement Pipe

Our service being ready, we use it to enhance our pipe.

import{Pipe,PipeTransform}from'@angular/core';import{TranslationService}from'./translation.service';import{Observable}from'rxjs';@Pipe({name:'filter'})exportclassFilterPipeimplementsPipeTransform{constructor(privatetranslationService:TranslationService){}transform(lang:string):Observable<string>{returnthis.translationService.translate(lang);}}

Which by the way can be use with the help of the async pipe in a template (in following example, lang is a public string variable of the component)

<textarea[value]="lang | filter | async"></textarea>

Update Pipe Test

Locally I’m still able to run my test without errors but, because we are now injecting a service in our pipe, if we open the related unit test we notice a TypeScript error on the constructor TS2554: expected 1 arguments, but got 0 . To fix this we have now to either inject the service or mock it.

Resolving Service In Test

You can either resolve the service via the inject function or TestBed . Because the first solution didn’t worked out for me, the second one was my fallback.

import{FilterPipe}from'./filter.pipe';import{TestBed}from'@angular/core/testing';import{TranslationService}from'./translation.service';describe('FilterPipe',()=>{beforeEach(()=>{TestBed.configureTestingModule({providers:[TranslationService]});});it('create an instance',()=>{constservice:TranslationService=TestBed.get(TranslationService);constpipe=newFilterPipe(service);expect(pipe).toBeTruthy();});});

Mock Service

Another solution, the one I actually finally applied, is creating a mock of the service instead of providing it.

import{FilterPipe}from'./filter.pipe';import{of}from'rxjs';import{TranslationService}from'./translation.service';describe('FilterPipe',()=>{lettranslationServiceMock:TranslationService;beforeEach(()=>{translationServiceMock={translate:jest.fn((lang:string)=>of('Awesome'))}asany;});it('create an instance',()=>{constpipe=newFilterPipe(translationServiceMock);expect(pipe).toBeTruthy();});});

Test Pipe Transform

So far we were able to test that our pipe can be created even if it relies on a service but we are still not effectively testing its outcome. Therefore, here is the final piece, in which I use the mock of the service. Basically, once the pipe is created, we can access its transform method and proceed with some common testing.

import{FilterPipe}from'./filter.pipe';import{of}from'rxjs';import{take}from'rxjs/operators';import{TranslationService}from'./translation.service';describe('FilterPipe',()=>{lettranslationServiceMock:TranslationService;beforeEach(()=>{translationServiceMock={translate:jest.fn((lang:string)=>of('Awesome'))}asany;});it('create an instance',()=>{constpipe=newFilterPipe(translationServiceMock);expect(pipe).toBeTruthy();});it('should translate',()=>{constpipe=newFilterPipe(translationServiceMock);pipe.transform('en').pipe(take(1)).subscribe((text:string)=>{expect(text).not.toBe(null);expect(text).toEqual('Awesome');});});});

Summary

It still takes me a bit to find the right testing setting for projects, specially when they are new, but as soon as everything is in place as soon as I can access the nativeElement to perform queries, as I would do in a Web Component, I feel more comfortable and it began to be fun 😁.

Stay home, stay safe!

David

Cover photo by Guillaume TECHER on Unsplash

Gatsby Tricks: Viewport, CSS Modules Transition And i18n Tricks

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-four days left until hopefully better days.

Today I developed further the new website I’m currently building with Gatsby for a new side project we have with two friends. I discovered some new tricks, that’s why there are the inspiration for my today’s diary entry.

Overriding Viewport

After a couple of hours of development, I had a basic website ready and even had developed the welcome page. I tested it across browsers and simulated different devices in Chrome, everything went fine until I tried out iPads.

I was a bit surprise because it did worked out everywhere else. As a skeptical person, of course my first reaction was “that’s probably a Chrome bug” (🤣), therefore I opened XCode and ran the same test on the simulator. To my surprise, the problem was still happening.

Concretely the following was happening.

Browser left ok vs iPad right not ok

It took me a bit to finally figure out what was the problem but finally my spider-sense kicked in and I had the feeling that the problem was linked to the specification of the viewport in the head of the page.

At first, I tried to solve the issue while injecting the meta information in the seo.js page using React Helmet. Unfortunately this didn’t worked out, as doing so, the page contained two meta information for the viewport and therefore mine was ignored. Finally, I found the solution in the Gatsby’s documentation. To solve my issue I had to copy the default cached file .cache/default-html.js to a new file src/html.js . Doing so, on each build, Gatsby will use my local configuration instead of the default one. Once copied I just had to modify the viewport and my problem was gone 🥳.

<metaname="viewport"content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>

CSS Modules Transition

My two colleagues, both designers, have always great design ideas. Often simple and effective but the more we collaborate together, the more we begin to add tiny animations to make everything cool 😎.

In this project I’m using CSS modules and to make some text appears, I had to use the property transition .

h1{opacity:0;visibility:hidden;transition:opacity0.25s0.8sease-in;&.animate{opacity:1;visibility:inherit;}}

To my surprise, again, what a day, transition didn’t worked out at all. I quickly noticed that the reason behind that was the opacity was converted to a scoped CSS. To be honest, I don’t know exactly why 🤷‍♂️. Anyway I figured out that by forcing its importation without transforming the property it solved my issue.

:global{transition:opacity0.25s0.8sease-in;}

And speaking of this transition , did you now that second time parameter is the delay after which the transition should happens? The above can be read as: After 0.8 seconds, start a transition on the opacity property for 0.25s and do that slowly at the begin. Pretty cool 😁.

i18n formatting

I shared some learnings regarding Gatsby and i18n in a previous blog post about internationalization but today I had to use React-intl a bit more.

I notably learned how to style a text. For example, let’s say we want to display a title “hello world”.

module.exports={"intro":"hello <strong>world</strong>",}

We can either split it in two different translations or take advantages of the React-intl FormattedMessage component to style our text.

<h1><FormattedMessageid="intro"values={{strong:(...chunks)=><strong>{chunks}</strong>,
}}/>
</h1>

Summary

Nothing here which might send humanity to Mars but still, to me, small steps and small learning are rewarding too and these make my day. I hope this blog post was enjoyable to you and that it might help you in the future, who knows.

Stay home, stay safe!

David

Cover photo by Ronald Ladines on Unsplash

Takeover The Cordova Facebook Plugin Maintenance

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-three days left until hopefully better days.

It has been in my mind for a while now and I think that, even if this post is not really going to be trick, this series of article is the perfect time to announce that I am giving up being the active maintainer of the Cordova Facebook Plugin.

This entry will be a bit more metaphysical than usual, I will tell you my story, how and why I ended up being the maintainer and also present what I did as such (not that much actually).

But more important, I would rather like not to sunset my duties without anyone taking over the shift.

Therefore, if you are up to become the new active maintainer, ping me🙏.

How Did I Become The Maintainer?

Funny story, I have almost no clue of objective-C. I did have a Java background, that’s why I can understand and write Android code but the iOS part? Mostly interpretation and using spider-sense 😉.

You may ask yourself, but then, how come did you became the maintainer of a Cordova plugin?

Three or four years ago (times fly), after a corporate career as junior and then senior developer, business analyst, project manager, team leader, company manager etc. etc. I was looking for something new and decided to try to build my own “startup”. Nowadays I use the term “startup” with quotation marks because at the end of the day, I was just a guy alone trying to do his best.

This “startup” was Fluster, a simple search for roommates and flats platform which I created because finding a new place to live in Zürich freaking sucks (excuse my french) and which, by the way, I am also about to sunset really soon.

This platform was a mobile application developed with Ionic (early version 4) and Cordova available in the App Store, Google Play and as a Progressive Web Apps.

To design its UX and develop it, I mostly decided to rely on the Facebook login and platform for two reasons:

  1. When it goes to mobile devices, I don’t like to enter data. I also don’t like when the onboarding takes to much steps and is slow
  2. Back then, Tinder was really big

After a year or something, I added a Google Login support too but most of the users were still using the Facebook one.

Long story short, if I remember correctly, there was one day a bug in the Cordova Facebook Plugin which could had only been solved by updating the Facebook SDK. Jeduan Cornejo, the author of the plugin, was not interested anymore to maintains it, therefore I provided a pull request, he granted me as a maintainer, I merged my own pull request and that was it, it was the begin of my activities.

Looking back at the history of the CHANGELOG I notice that it actually all began around two years ago, somewhere around March 24th 2018. So I’ve been a maintainer for two years.

Why I Gave Up?

First of all, I gave up being a maintainer of this plugin because I don’t use it anymore and this since probably more than a year as I stopped being active in Fluster (the application, not the company) more than a year ago. It means that I remained the maintainer even without having any interest to it for quite a while. I don’t want to be praised for that or anything else I did regarding these activities, I’m just saying it out at loud to explain my motivation, don’t misunderstand me about this.

Furthermore, I don’t want to use it anymore. Since Fluster, I never ever implemented Facebook login again. I even don’t implement the option to share to this platform in all my personal recent work.

Facebook (The Company) Sucks

Excuse my french again 😅. Most probably, almost no one nowadays is going to say that he/she likes Facebook. We are all aware that Facebook, the company, is evil. Between scandals, leaks, data breaches, data sold, etc. nothing can probably change this really soon. I say “probably” because for example in the 90s Microsoft used to be known as evil too but they managed to make good things to become respectable. Before them, IBM used to be seen as evil too but nowadays there are just…well there are just still alive, like always. IBM is going to be there somewhere selling super complicated solutions for ever 🤣.

But, regarding my above point on view on Facebook, here are two things you might not know:

Worst Communication Ever

At the time all data breaches appeared in the news, Facebook began to chase their leaks in order to fix these, or maybe to hide more of these, who knows. From my external developer eyes who was using there API, I noticed that they did so because they began to ship updates really fast, so fast that sometimes they even deprecated services and only released the release notes informing you that these were down afterwards. I remember having seen things being stop and reading about it one day later.

They also did changes to their server infrastructure and sometimes stopped them for days without any prior information or any explanation afterwards.

I will never forget one particular incident. Suddenly it was not possible anymore to display users profile pictures. After tracking down the problem and spent several hours in the issue tracker of Facebook, I finally figured out that one issue was really close to mine. At some point, we were more than 500 people around the world following the exact same issue. During the night, Facebook decided to change the status of the issue from “open” to “in progress” and finally solved it around two days later. I never ever heard or read any explanations about what happened. I don’t even ask for apologies, I mean problems happens, but no comments, no messages, nothing, that's a pity.

Since that day I always tell to my self, sort of my private joke, that the communication of Facebook should be taught in schools as an example of how to communicate badly with developers.

Facebook Do Not Delete Ads Profile

As much as I criticize Facebook, I am also, even more strongly, criticize my self.

When I developed Fluster I tried to reach my audience mostly with Facebook and Google ads. Probably because that’s what I learned in previous jobs. Concretely I invested of couple of thousand dollars of my private savings over several months to try to grow my business. Even if I was believing in it, I kind of feel bad about it. I am really not proud of my self to have tried to use ads, I mean, it’s ads, it is not cool and money can be use so much more wisely.

But at least the good point of this, is that I learned from what I consider as I a personal error and it probably helped me to become the person I’m today, or at least helped me to be more aware of what I want in life, what I want to achieve and more important, how.

That being said and here is my point. Even though my decision was bad, I should be able to delete my data. In Europe there is GDPR, which we don’t really have in Switzerland, but still, I should be able to request a deletion of my data right? Guess what, Facebook don’t wand and don’t allow deletion of Ads related profiles. No matter what, your data are going to stay there for ever.

Oh and by the way, Google is not better on that point, you cannot delete Google Ads profile neither.

Maintainer Duties

Bad or good, the Facebook login is still used by a lot of applications, more than 6'000 downloads a week on npm, probably a lot of these are doing some good and it still needs a maintainer.

As such, I mostly did the following activities:

  • Taking care of maintaining a clear issues tracker. Most the time, issues were actually support request, therefore I kindly asked developers to use other platform to handle these as otherwise it would have made the real issues really not trackable and not noticeable
  • Updating the documentation according developer inputs
  • Merging important Pull Requests
  • Updating the Facebook SDK
  • Providing a sample repo to help with the testing but more important, to help reproduce submitted issues

Of course you will be able to organize yourself as you wish and I even now think that having a new active maintainer might bring some fresh ideas to the plugin.

Summary

This opinionated blog post contained a lot of Facebook bashing, probably too mush, but facts don’t speak currently for this company. Hopefully they will be able in the future, as Microsoft and other did, to gain again a credibility, hopefully by giving their talented employees the opportunities to improve things which I am convinced they all are eager to change.

Stay home, stay safe!

David

Cover photo by Thought Catalog on Unsplash

Protect Your HTTP Firebase Cloud Functions

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-two days left until hopefully better days.

Last year I developed an application for a foundation which has for goal to help people from a certain age. Mostly for administrative reason, the project was not yet released publicly.

Recently we noticed some similarities in its goal and the current lockdown situation. That’s why I was asked to create a spin-off, containing the useful features, which can be unleashed quickly because it would be useful!

I spent my Saturday morning “cloning” our Firebase Cloud infrastructure and had to protect the new HTTP Functions I just deployed online. That’s why I had the idea to write this brief article about the subject.

Authorization Bearer

When you create Firebase Cloud function which can be triggered or called through an HTTP request, the function is public and available on the internet. As far as I know, its generated URL does not contains any random id or other hash which would make the route not predictable, therefore you have to find a way to protect it.

That’s why the solution, in my point of view, is to authenticate each requests with an authorization token.

One way of solving this is adding a constant key in your code. For example, if we have deployed the following function.

import*asfunctionsfrom'firebase-functions';exportconsthelloWorld=functions.https.onRequest(async(request,response)=>{response.json({result:`Hello World.`});});

We can create a function to validate a bearer which would have to be provided for each requests.

import{Request}from'firebase-functions/lib/providers/https';asyncfunctionvalidBearer(request:Request):Promise<boolean>{constkey:string='our-key-value';constauthorization=request.get('Authorization');constsplit=authorization?authorization.split('Bearer '):[];constbearerKey=split&&split.length>=2?split[1]:undefined;returnkey===bearerKey;}

And use it to extend our HTTP function with a test.

exportconsthelloWorld=functions.https.onRequest(async(request,response)=>{constisValidBearer:boolean=awaitvalidBearer(request);if(!isValidBearer){response.status(400).json({error:'Not Authorized'});return;}response.json({result:`Hello World.`});});

For the key we can of course use something like a password or dumb keyword as I used above but it would be more secure to use for example a Version 4 UUID. There are many tools to generate such but I used today https://www.uuidgenerator.net which perfectly did the job.

constkey='975dd9f6-4a89-4825-9a6d-deae71304a29';

As a result, our HTTP route is now protected and only accessible if an authorization is provided.

#!/bin/sh
curl -i-H"Accept: application/json"-H"Authorization: Bearer 975dd9f6-4a89-4825-9a6d-deae71304a29"-X GET  https://us-central1-yolo.cloudfunctions.net/helloWorld

Firebase Environment Variables

Protection is cool, but is our protection protected 🧐? If our code is published as an open source software, our key is going to be exposed. Moreover, it ain’t really cute to handle a key in middle of the code. But there is a solution.

Firebase provides the ability to define not publicly exposed environment variables which can be read from Cloud functions.

To define our above key as such we can run the following command with the help of the Firebase CLI.

#!/bin/sh
firebase functions:config:set hello.world.key="975dd9f6-4a89-4825-9a6d-deae71304a29"

Last remaining things to do is replacing our constant with the new variable.

constkey=functions.config().hello.world.key;

And that’s it, our HTTP Firebase Cloud function is protected 🎉.

Altogether

Just in case you would need the above code in one block, here is it altogether:

import*asfunctionsfrom'firebase-functions';import{Request}from'firebase-functions/lib/providers/https';asyncfunctionvalidBearer(request:Request):Promise<boolean>{constkey=functions.config().hello.world.key;constauthorization=request.get('Authorization');constsplit=authorization?authorization.split('Bearer '):[];constbearerKey=split&&split.length>=2?split[1]:undefined;returnkey===bearerKey;}exportconsthelloWorld=functions.https.onRequest(async(request,response)=>{constisValidBearer:boolean=awaitvalidBearer(request);if(!isValidBearer){response.status(400).json({error:'Not Authorized'});return;}response.json({result:`Hello World.`});});

Summary

Firebase is so handy 😃. If this project or any other work I’m publishing as open source piece of software might interest you, follow me on Twitter and say hi. But more important:

Stay home, stay safe!

David

Cover photo by Andre Hunter on Unsplash

Create A Menu For Your Gatsby Website Without Libs

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty-one days left until hopefully better days.

I have developed my personal website with Gatsby but without using any templates or any design libraries. No particular reason, I just like in my personal projects to restrict the use of dependencies when I can. Thereby, I had to build my own simple menu for the navigation which I’m sharing with you today.

daviddalbusco.com

Menu Component

The menu is nothing else than a component which contains a state to reflect its status, being open or close. Its only particularity is the fact that we have to expose a function open() in order to be able to trigger its opening from the navigation, in my case, from the hamburger menu in the navigation bar. For such purpose we use Hooks API useImperativeHandle to customize our instance.

importReact,{useImperativeHandle,useRef}from"react"import{Link}from"gatsby"importstylesfrom"./menu.module.scss"classMenuextendsReact.Component{constructor(props){super(props)this.state={open:false,}}render(){return(<divrole="button"tabIndex="0"className={`${styles.menu}${this.state.open?`${styles.open}`:""}`}onClick={()=>this.close()}onKeyDown={()=>this.close()}><Linkto="/"><h1>Home</h1></Link><Linkto="/#portfolio"><h1>Portfolio</h1></Link><Linkto="/#about"><h1>About</h1></Link><Linkto="/blog"><h1>Blog</h1></Link><Linkto="/#contact"><h1>Contact</h1></Link></div>
)}close(){this.setState({open:false})}open(){this.setState({open:true})}}exportdefaultReact.forwardRef((props,ref)=>{constmenuRef=useRef()useImperativeHandle(ref,()=>({open(){menuRef.current.open()},}))return<Menuref={menuRef}{...props}/>
})

For the styling, I use CSS modules in the above example. The menu is as a fixed div which covers the all screen and is per default not visible. With the help of the state, I apply a style to modify its opacity with a small transition to make the menu appears a bit smoothly.

.menu{position:fixed;top:0;left:0;height:100%;width:100%;background:#ffffff;z-index:1031;display:flex;flex-direction:column;justify-content:center;align-items:center;visibility:hidden;opacity:0;transition:all0.35s;cursor:pointer;&.open{visibility:visible;opacity:1;}a{pointer-events:all;}}

Integrating The Menu

To use the menu in the navigation, I have integrated in its related component. The only particularity I had to develop is assigning a reference in order to be able call the method open() from an onClick function.

Note that as the menu inherits a fixed style, it can be integrated at any level of the DOM.

importReactfrom"react"importMenufrom"./menu"classNavigationextendsReact.Component{render(){return(<><buttononClick={()=>this.toggleMenu()}>OpenMenu</button>
<Menuref={el=>(this.childMenu=el)}/>
</>
)}toggleMenu(){this.childMenu.open()}}exportdefaultNavigation

That’s it, nothing more, nothing less, we have developed a custom menu for our website without any JavaScript dependencies 😁.

Summary

I like to use Gatsby for website. It has many advantages and I think it is also cool for me that it uses React as I like on a weekly basis to not stick to the same technologies. A bit of Angular, a bit of React, a bit of Web Components with StencilJS, a bit of vanilla JavaScript, everything is only fun 😉.

Stay home, stay safe!

David

Cover photo by Catherine Heath on Unsplash


Create A Modal For Your Angular App Without Libs

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twenty days left until hopefully better days.

The other day we were building a quick proof of Angular concept with one of my client’s teammate for which we had to display something in a modal. Instead of installing and using some design libraries to solve this requirement, I estimated that it would need almost the same effort to create quickly one (I was not wrong on that one).

Yesterday I shared another solution to create a custom menu for a Gatsby website without any dependencies and that’s why I had the idea today to share the following tutorial.

Service

The modal has two states: open and close . That’s why, before anything else, we create a service using the Angular CLI (command: ng g service modal) which we are going to use across our application to trigger either its opening or closing.

import{Injectable}from'@angular/core';import{BehaviorSubject,Observable}from'rxjs';@Injectable({providedIn:'root'})exportclassModalService{privatedisplay:BehaviorSubject<'open'|'close'>=newBehaviorSubject('close');watch():Observable<'open'|'close'>{returnthis.display.asObservable();}open(){this.display.next('open');}close(){this.display.next('close');}}

Note that at the end of the day, you can use a boolean or an enum if you rather like, or a Subject instead of BehaviorSubject . What does matter is to be able to maintain the two states of the modal.

Modal

We create a new component for our modal using the Angular CLI (ng c component modal ).

Code

The component code contains a variable, an Observable which we instantiate to watch out the state of the modal, and exposes a function which we can use to close the modal.

import{Component,OnInit}from'@angular/core';import{Observable}from'rxjs';import{ModalService}from'../modal.service';@Component({selector:'app-modal',templateUrl:'./modal.component.html',styleUrls:['./modal.component.scss'],})exportclassModalComponentimplementsOnInit{display$:Observable<'open'|'close'>;constructor(privatemodalService:ModalService){}ngOnInit(){this.display$=this.modalService.watch();}close(){this.modalService.close();}}

Template

In the container we define a section to cover the all screen when the modal is opened and we define a child, a div , to constrain our modal to a certain size.

Note that I stop the event propagation on the container just in case you would like to add some actions inside the modal, for example a form with a submit button.

<ng-container*ngIf="display$ | async as display"><section[class.open]="display === 'open'"(click)="close()"><div(click)="$event.stopPropagation()"><buttonclass="close"type="button"(click)="close()">X</button><h1>Hello World</h1></div></section></ng-container>

Style

To make the modal appears smoothly we use a brief transition on the property opacity . We also define some box-shadow and and background for the backdrop to make thing just a bit styled.

section{visibility:hidden;opacity:0;&.open{visibility:inherit;opacity:1;}display:block;position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.2);transition:opacity250msease-in;>div{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);height:200px;width:300px;background:white;padding:64px;border-radius:8px;box-shadow:008px4pxrgba(0,0,0,0.1);}button.close{background:none;color:inherit;border:none;font:inherit;cursor:pointer;outline:none;position:absolute;top:0;left:0;padding:16px;}}

Declaration

For simplicity reason I didn’t created a module to load the modal but if you would follow this tutorial for a real life application, I would advise you to do so in order to lazy load it. Meanwhile, in this blog post, we add our component to the declarations of our main module app.module.ts.

@NgModule({declarations:[AppComponent,ModalComponent],...})exportclassAppModule{}

Additionally, we also use our component in our template only once for our all application, as we only manage a single instance and state, for example in app.component.html .

<router-outlet></router-outlet><app-modal></app-modal>

Usage

We are set, everything is developed, we just need now to effectively test it. That’s why we add a button to our app which triggers the modal opening.

For example, we can declare a new function open() in one of our component in which we are looking to trigger the modal opening.

import{Component}from'@angular/core';import{ModalService}from'../modal.service';@Component({selector:'app-home',templateUrl:'home.page.html',styleUrls:['home.page.scss'],})exportclassHomePage{constructor(privatemodalService:ModalService){}open(){this.modalService.open();}}

And link the method in the related template.

<button(click)="open()"><h2>Open</h2></button>

That’s it, nothing more, nothing less, we have developed a custom modal for our application without any JavaScript dependencies 😁.

Summary

Of course out of the box, a quickly custom made modal isn’t the most beautiful one you ever used or saw, but to me, what’s important to communicate is probably the fact that we don’t have always to rely on dependencies. The path is probably Peter Quill’s one, a bit of both 😉.

Stay home, stay safe!

David

Cover photo by Roger Burkhard on Unsplash

Add A Slider To You Angular App

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Nineteen days left until hopefully better days.

The other day I was upgrading the dependencies of one of my client’s application developed with Ionic.

To my surprise, the “fade” transition of the slider wasn’t working anymore. After a bit of digging, I discovered, for performance reason, that the animations had been made optional and had to be solved by copying a piece of code in my application.

Moreover I also had to develop some design changes in its introduction process, which I did but actually not so straight forward as I assumed, as the slides are actually stacked when not displayed.

In short, it worked like a charm but that made me think, did you know that you can actually use the core Web Component of DeckDeckGo, our open source editor for presentations, to add a slider to any web applications developed with or without any modern frontend frameworks?

Probably not 😉.

That’s why I’m going to display in this blog post, as for example, how you can do so in an application developed with Angular.

Add a slider to your application

Installation

For this realization, we are going to need our core. Moreover, because our solution is based on template, we are going to need at least one of them. As we are looking to implement a slider in which we are going to display information, the easiest template to use is our title template which centers the content.

npm i @deckdeckgo/core @deckdeckgo/slide-title --save

Furthermore, it may complains at build time that some type definitions are missing no worries, just add these to your development dependencies.

npm i @deckdeckgo/types --save-dev

Configuration

Once installed we need to import the components. These are developed with StencilJS which provides a handy documentation on the subject.

That being said, Angular won’t resolve the libraries if we only import these as the following.

import'@deckdeckgo/core';import'@deckdeckgo/slide-title';

That’s why we are using the provided loaders option.

import{defineCustomElementsasdeckGoCore}from'@deckdeckgo/core/dist/loader';import{defineCustomElementsasdeckGoSlide}from'@deckdeckgo/slide-title/dist/loader';deckGoCore(window);deckGoSlide(window);

Furthermore, because Angular is not going to recognize our templates, we have to instruct it to support custom schemas. This can be set in any modules or globally in app.module.ts .

import{BrowserModule}from'@angular/platform-browser';import{CUSTOM_ELEMENTS_SCHEMA,NgModule}from'@angular/core';import{AppRoutingModule}from'./app-routing.module';import{AppComponent}from'./app.component';@NgModule({declarations:[AppComponent],imports:[BrowserModule,AppRoutingModule],providers:[],bootstrap:[AppComponent],schemas:[CUSTOM_ELEMENTS_SCHEMA]})exportclassAppModule{}

Usage

Our components are installed and configured, we can add the slider which is nothing else that a container <deckgo-deck/> , and its children, the slides, <deckgo-slide-title> .

<deckgo-deck><deckgo-slide-title><h1slot="title">Add</h1></deckgo-slide-title><deckgo-slide-title><h1slot="title">a slider</h1></deckgo-slide-title><deckgo-slide-title><h1slot="title">to your app</h1></deckgo-slide-title></deckgo-deck>

And that’s it, the slider is in place 🎉.

Our slider is ready

Customization

You might want to apply some styling to your slide, here are some options.

Hide Pager

A pager is useful for a presentation but most probably not for a slider in an application. That’s why you might want to hide it using the CSS4 variables --pager-display .

<deckgo-deckstyle="--pager-display: none;"></deckgo-deck>

Hidden pager

Transition

You might want to use another transition effect. Our core doesn’t handle yet a lot of different animation, Pull Requests are welcomed, but it does already offers a fade effect or none .

<deckgo-deckstyle="--pager-display: none;"transition="fade"></deckgo-deck>

Fade transition

Navigation

If we would implement the slide as an introduction to your application, there is a good chance that we would be interested to add a next and a skip buttons. Our core also emits multiple events and exposes navigation methods, let’s try to use these.

For such purpose, we add these two buttons, we are identifying our deck with #deck and are listening to two deck transitions events respectively slideNextDidChange and slidePrevDidChange .

<deckgo-deckstyle="--pager-display: none;"transition="fade"#deck(slideNextDidChange)="updateLastSlide()"(slidePrevDidChange)="updateLastSlide()"><deckgo-slide-title><divslot="title"><h1>Add</h1></div></deckgo-slide-title><deckgo-slide-title><divslot="title"><h1>a slider</h1></div></deckgo-slide-title><deckgo-slide-title><divslot="title"><h1>to your app</h1></div></deckgo-slide-title></deckgo-deck><divstyle="position: absolute; bottom: 0; right: 0;"><button(click)="skip()"*ngIf="!lastSlide">Skip</button><button(click)="next()">Next</button></div>

Finally we implement the related methods while using a ViewChild reference on the deck which exposes the methods we need.

import{Component,ElementRef,ViewChild}from'@angular/core';import{defineCustomElementsasdeckGoCore}from'@deckdeckgo/core/dist/loader';import{defineCustomElementsasdeckGoSlide}from'@deckdeckgo/slide-title/dist/loader';deckGoCore(window);deckGoSlide(window);@Component({selector:'app-root',templateUrl:'./app.component.html',styleUrls:['./app.component.scss']})exportclassAppComponent{@ViewChild('deck')deck:ElementRef;lastSlide=false;asyncskip(){console.log('Go to next page');}asyncnext(){constend=awaitthis.deck.nativeElement.isEnd();if(end){awaitthis.skip();return;}awaitthis.deck.nativeElement.slideNext();}asyncupdateLastSlide(){constindex=awaitthis.deck.nativeElement.getActiveIndex();constlength=awaitthis.deck.nativeElement.getLength();this.lastSlide=index===length-1;}}

And that’s it, it works out 😁.

Navigation with our core

Summary

It isn’t our goal to provide the most complete core slider with a zillion options as it would not serve our main focus but, that being said, as you may have noticed, I think it offers a valid alternative to implement quickly a slider in any modern web applications.

If you want to know more about it, check out our documentation or give a try to our editor for your next presentations!

Stay home, stay safe!

David

Cover photo by Persnickety Prints on Unsplash

Test Angular Components and Services With HTTP Mocks

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Eighteen days left until hopefully better days.

The other day I was writing some Angular tests for a new project of one my client and I was about to mock my service function when suddenly the idea hit me: what if instead of mocking my service functions, I would mock the HTTP requests globally for all my tests with the goal to test also my services logic at the same time as I would test my components 🤔

I was able to achieve this goal and that’s why I’m sharing this learning in this new blog post.

Setup

Let’s define a simple setup as example.

We have a service which exposes a single HTTP request. For the purpose of this tutorial, we can use the amazing free and open source API provided by the Dog API.

import{Injectable}from'@angular/core';import{HttpClient}from'@angular/common/http';import{Observable}from'rxjs';exportinterfaceDog{message:string;status:string;}@Injectable({providedIn:'root'})exportclassDogService{constructor(privatehttpClient:HttpClient){}randomDog():Observable<Dog>{returnthis.httpClient.get<Dog>(`https://dog.ceo/api/breeds/image/random`);}}

And a component which displays the random doggo.

import{Component}from'@angular/core';import{Observable}from'rxjs';import{Dog,DogService}from'../dog.service';@Component({selector:'app-dog',template:`<img *ngIf="doggo$ | async as doggo" 
                  [src]="doggo.message">`})exportclassDogComponent{doggo$:Observable<Dog>;constructor(privatedogService:DogService){this.doggo$=dogService.randomDog();}}

If you test this component, rendered in your browser you should discover a good doggo like this sweet bulldog.

Test Services With HTTP Requests

As we are going to develop a mock for our HTTP requests, we can begin first by testing our service.

To test our service we are going to take advantages of the HttpClientTestingModule provided by Angular as Josué Estévez Fernández described in his brillant article about Angular Testing.

Basically, what we do is subscribing to our service exposed function randomDog() in order to except a result which should be our mocked data. To triggers the result we instruct the controller that we want to perform only one query using exceptOne and finally we flush the response with the mock data which will cause our observer to resolve.

import{TestBed}from'@angular/core/testing';import{HttpClientTestingModule,HttpTestingController}from'@angular/common/http/testing';import{Dog,DogService}from'./dog.service';exportconstmockDog:Dog={message:'https://images.dog.ceo/breeds/hound-basset/n02088238_9815.jpg',status:'success'};describe('DogService',()=>{lethttpTestingController:HttpTestingController;letservice:DogService;beforeEach(()=>{TestBed.configureTestingModule({providers:[DogService],imports:[HttpClientTestingModule]});httpTestingController=TestBed.get(HttpTestingController);service=TestBed.get(DogService);});afterEach(()=>{httpTestingController.verify();});it('should be created',()=>{expect(service).toBeTruthy();});it('random should should provide data',()=>{service.randomDog().subscribe((dog:Dog)=>{expect(dog).not.toBe(null);expect(JSON.stringify(dog)).toEqual(JSON.stringify(mockDog));});constreq=httpTestingController.expectOne(`https://dog.ceo/api/breeds/image/random`);req.flush(mockDog);});});

If you run the tests (npm run test ) these should be successfull.

Test Components With HTTP Requests Mock

Now here comes the fun part 😉. Our goal is to test our component without “touching” the service but by mocking all HTTP requests used by these.

For such purpose we create a custom HttpInterceptor , as sanidz explained in his/her super article about Mocking Interceptor, which should take care of, well, intercepting the requests and overriding our calls with our mock data when we have the need. In our example, if the DOG api is hit, we want to answer with the mock data we have defined earlier to test our service.

import{Injectable,Injector}from'@angular/core';import{HttpEvent,HttpHandler,HttpInterceptor,HttpRequest,HttpResponse}from'@angular/common/http';import{Observable,of}from'rxjs';import{mockDog}from'./dog.service.spec';@Injectable()exportclassHttpRequestInterceptorMockimplementsHttpInterceptor{constructor(privateinjector:Injector){}intercept(request:HttpRequest<any>,next:HttpHandler):Observable<HttpEvent<any>>{if(request.url&&request.url.indexOf(`https://dog.ceo/api/breeds/image/random`)>-1){returnof(newHttpResponse({status:200,body:mockDog}));}returnnext.handle(request);}}

When creating the above interceptor you might face a typescript error regarding the decorator. If it is the case you can solve it by enabling experimentalDecorators in your tsconfig.spec.json .

{"extends":"./tsconfig.json","compilerOptions":{"outDir":"./out-tsc/spec","experimentalDecorators":true,<-enableexperimentaldecorator"types":["jasmine","node"]},"files":["src/test.ts","src/polyfills.ts"],"include":["src/**/*.spec.ts","src/**/*.d.ts"]}

Our interceptor being set, we can now test our component. One again we are going to use the HttpClientTestingModule but moreover we are providing our HTTP interceptor for the configuration of the test. Doing so, on each request, our interceptor will be triggered and we are going to able to mock our data. We are also using these to ensure that our component’s image match the one we have defined as mock.

import{async,ComponentFixture,TestBed}from'@angular/core/testing';import{HttpClientTestingModule}from'@angular/common/http/testing';import{HTTP_INTERCEPTORS}from'@angular/common/http';import{HttpRequestInterceptorMock}from'../http-request-interceptor.mock';import{mockDog}from'../dog.service.spec';import{DogComponent}from'./dog.component';describe('DogComponent',()=>{letcomponent:DogComponent;letfixture:ComponentFixture<DogComponent>;beforeEach(async(()=>{TestBed.configureTestingModule({declarations:[DogComponent],imports:[HttpClientTestingModule],providers:[{provide:HTTP_INTERCEPTORS,useClass:HttpRequestInterceptorMock,multi:true}]}).compileComponents();}));beforeEach(()=>{fixture=TestBed.createComponent(DogComponent);component=fixture.componentInstance;fixture.detectChanges();});it('should create',()=>{expect(component).toBeTruthy();});it('should render image',async()=>{constimg:HTMLImageElement=fixture.debugElement.nativeElement.querySelector('img');expect(img).not.toBe(null);expect(mockDog.message===img.src).toBe(true);});});

That’s it, it is super, furthermore than being able to test our component we are also able to test our service at the same time 🥳.

Summary

I’m really grateful to have find the useful tips from Josué Estévez Fernández and sanidz. The setup is now in place I can really progress in the development of the project while being able to add tests which made sense, at least to me 😉. I hope this approach will help you some day hopefully too.

Stay home, stay safe!

David

Cover photo by Josue Isai Ramos Figueroa on Unsplash

Merge Two Objects And Array To Object In JavaScript

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Seventeen days left until hopefully better days.

To be really honest with you, I did not had that much idea for today’s blog post. For my defense, this is the eighteenth blog post I write every day in a row, it might explains my lack of today’s inspiration 😅.

That being said, I will share with you the following two tricks which I find useful.

Merge Two Objects

Thanks to the introduction of Spread Operator in ES6, it is now more than ever before really easy to merge two objects. No more loops, comparison or rocket science, the merge operation can be written in a single line of code.

It is also worth to notice that in case both objects would contain the same key, the value of the last object, “the one on the right in the line of code”, is the winning value.

constbruno={sing:true,song:'Treasure'};constratm={type:'band',song:'Bombtrack'};constresult={...bruno,...ratm};console.log(result);// -> {sing: true, song: "Bombtrack", type: "band"}

Array To Object

In order to transform an array to an object in JavaScript, I did not find so far the magic line of code which would do all the job in one single line of code. If you do know how to achieve this, ping me with your comments and best pieces of code, I would love to hear about it 😁.

Meanwhile, I use the reduce function. Fun fact, that’s probably one of the few times that I am using it. I use often forEach , map , filter and find but reduce , truly rarely.

// {name: string, genre: string}[]constbands=[{name:'Ratm',genre:'rock'},{name:'Bruno',genre:'Pop'}];// {ratm: string, bruno: string}constartists=bands.reduce((obj,item)=>{obj[item.name]=item.genre;returnobj;},{});console.log(artists);// {Ratm: "rock", Bruno: "Pop"}

Summary

It is interesting to notice that the more the language evolves, the more basic operations become easy to implement with few lines of code. Also interesting is the fact that sometimes it takes time to these new options to be rolled out to me. ES6 has been around for a while and I am still writing about it in 2020 😉.

Stay home, stay safe!

David

Cover photo by Ludovic Migneault on Unsplash

JSX For Angular Developers

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Sixteen days left until hopefully better days.

At first I wasn’t that much a fan of the JSX syntax when I discovered it while developing my first Web Components with Stencil. I was missing the Angular HTML templates.

Nowadays? I might change my mind in the future again, but after having developed such an eco-system as DeckDeckGo and having even learned React, I can definitely say that I actually feels all the contrary, I love JSX ❤️. Even probably more these days as I am developing Angular clients’ projects on a weekly basis.

That’s why I had this idea to write a really brief and I hope beginner friendly introduction to JSX as used in Stencil or React for Angular developers.

JSX vs HTML Templates

If you write an Angular application, commonly you are going to separate your components in layers and even probably three separate files: the code (TypeScript), the style (CSS) and the template (HTML, the GUI).

import{Component}from'@angular/core';@Component({selector:'app-my-component',templateUrl:'./my-component.component.html',styleUrls:['./my-component.component.scss']})exportclassMyComponentComponent{}

And the related template:

<div>Hello, World!</div>

With JSX, regardless if Stencil or React, you have this separation of concern too but you are not going to separate your template and code in two separate files. Everything is commonly packed in on file, even in a same class or function .

The separation of concern occurs on the code side. If you have a class , you will have to expose a method render() which returns what suppose to be, guess what, rendered. In short: “a method which renders your HTML code”.

import{Component,h}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css'})exportclassMyComponent{render(){return<div>Hello,World!</div>;
}}

If you have a function , then instead of render you will have a return method which follows the same behavior.

importReactfrom'react';constMyComponent:React.FC=()=>{return(<div>Hello,World!</div>
);};exportdefaultMyComponent;

Both Stencil and React do support class or function . These last type became or are becoming, I think, really popular in React thanks to the use and introduction of Hooks , which I am not going to cover in this article. If you are interested in a separate post about it, ping me! I still have many posts to write to fulfill my challenge 😆.

Note also that for the rest of this article, I will display the Stencil examples using class and the React one using functions .

Root Element

One important difference is the notion of root element. In Angular, you don’t really care about if. If your template contains a single root element or multiple ones, it compiles in any case.

<div>Hello, World!</div><div><p>Salut</p><p>Hallo</p></div>

In JSX, to the contrary, it does matter. Your component should be developed to handle such cases.

Therefore, our first solution might be to group our children under a single HTML node.

import{Component,h}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css'})exportclassMyComponent{render(){return<div><div>Hello,World!</div>
<div><p>Salut</p>
<p>Hallo</p>
</div>
</div>;
}}

That would work out but this would result on adding a not needed div tag, the parent one, to our DOM. That’s why both Stencil and React have their respective similar solution to this problem.

In Stencil you can use a Host element.

import{Component,h,Host}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css'})exportclassMyComponent{render(){return<Host><div>Hello,World!</div>
<div><p>Salut</p>
<p>Hallo</p>
</div>
</Host>;
}}

And in React you can use what is called a Fragment.

importReactfrom'react';constMyComponent:React.FC=()=>{return(<><div>Hello,World!</div>
<div><p>Salut</p>
<p>Hallo</p>
</div>
</>
);};exportdefaultMyComponent;

Finally, in Stencil, if you rather like not to use such container, you can return an array of elements. But I feel like, mostly for styling reason, that I used the above solution more often so far.

import{Component,h}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css'})exportclassMyComponent{render(){return[<div>Hello,World!</div>,
<div><p>Salut</p>
<p>Hallo</p>
</div>
];}}

States And Properties

In Angular public variables are these used in the templates and for which any changes are triggering a new rendering (“the changes are applied to the GUI”).

Variables made private are these which are used internally in the component and for which, no new rendering is needed.

Moreover there is also the Input decorator which is used to expose a variable as property of the component.

import{Component,Input}from'@angular/core';@Component({selector:'app-my-component',templateUrl:'./my-component.component.html',styleUrls:['./my-component.component.scss']})exportclassMyComponentComponent{@Input()count=0;odd=false;privateeven=false;inc(){// Render againthis.count++;this.odd=this.count%2===1;// Do not trigger a new renderthis.even=this.count%2===0;}

And corresponding template:

<div>Hello, World!</div><div>{{odd}} {{count}}</div>

In JSX, you find the same approach but kind of split in two categories, state and properties , for which, any changes will trigger a new render of the component. On the other side, if you have a variable which is neither one of these, then no render will be triggered again.

properties are kind of the corresponding idea to the @Input() fields, these are the exposed properties of the components.

states are kind of Angular public variables which have not been marked as inputs.

Concretely in Stencil you use decorator for such purpose.

import{Component,h,Host,Prop,State}from'@stencil/core';@Component({tag:'my-component',styleUrl:'my-component.css'})exportclassMyComponent{@Prop()count=0;@State()privateodd=false;even=false;inc(){// Render againthis.count++;this.odd=this.count%2===1;// Do not trigger a new renderthis.even=this.count%2===0;}render(){return<Host><div>{this.odd}{this.count}</div>
</Host>
;}}

While in React functions you are going to use hooks to handle states and interfaces to declare your properties.

importReact,{useEffect,useState}from'react';interfaceMyProps{count:number;}constMyComponent:React.FC<MyProps>=(props:MyProps)=>{const[odd,setOdd]=useState<boolean>(false);leteven=false;useEffect(()=>{// Render againprops.count++;setOdd(props.count%2===1);// Do not trigger a new rendereven=props.count%2===0;},[props.count]);return(<><div>{odd}{props.count}</div>
</>
);};exportdefaultMyComponent;

I now, I said I won’t cover hooks in this article, therefore let just summarize these as asynchronous functions, which observe or apply a change to a variable and in case of the hook dedicated to states, useState , trigger a new rendering if a change is applied to the observed variable.

Conditional Rendering

Angular exposes is own tags which have to be use in the templates to perform any logical operations, notably *ngIf for conditional rendering.

<div>Hello, World!</div><div*ngIf="odd">{{count}}</div>

One beauty of JSX is that you are not developing in a template, therefore you are using statements as you would do writing code.

In brief, a if is a if😉.

Only important thing to remember about conditional rendering: always return something! That’s why, if you do not want to render anything, I suggest to return undefined which will have for effect to add nothing to the DOM.

With Stencil:

render(){return<Host>{this.odd?<div>{this.odd}{this.count}</div> : undefined
}</Host>;
}

Or with React:

return(<>{odd?<div>{odd}{props.count}</div> : undefined
}</>
);

Moreover, you can either inline your condition as above or use it wisely in split render methods.

As in this Stencil example:

render(){return<Host>{this.renderLabel()}</Host>;
}privaterenderLabel(){returnthis.odd?<div>{this.odd}{this.count}</div> : undefined;
}

Or again in this React one:

return(<>{renderLabel()}</>
);functionrenderLabel(){returnodd?<div>{odd}{props.count}</div> : undefined;
}

Summary

There is so much left to say and to describe, but unfortunately I have to rush to make steps forwards in a useful, particularly in these special days, mobile application I am developing for a client.

If this appetizer made you eager to know more about JSX from an Angular point of view, let me know. I would be really happy to develop it further in several blog posts. And Like I said, I still got some more in order to accomplish my challenge 😃.

Stay home, stay safe!

David

Cover photo by Maël Renault on Unsplash

More JSX For Angular Developers

$
0
0

I share one trick a day until the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Fifteen days left until hopefully better days.

It is Saturday, I cleaned my flat, I could begin the writing of my today’s challenge blog post 😉.

Yesterday I published a brief introduction to JSX for Angular Developers. When I was about to finish writing, I noticed that there were still some materials which can be presented, that’s why I follow up with some more information regarding JSX, the syntax extension to JavaScript.

Data Binding

I should have mentioned it in my yesterday’s post, but as Angular use its own templating syntax, the one from JSX differs a bit.

Commonly you use double braces {{}} to bind values or brackets [] to set for examples conditional classes or pass variables to a component.

<div*ngIf="odd"[class.danger]="count > 1">{{count}}</div><app-my-componentcount="{{count}}"></app-my-component>

Meanwhile with JSX, you are going to use single braces {}. Furthermore we can also take advantages of the render or return functions to use, if we would like, temporary variables to make the code even a bit cleaner by avoiding some long inline expressions.

The above coded with Stencil:

render(){constmyClass=this.count>1?'danger':undefined;return<Host><divclass={myClass}>{this.count}</div>
<my-componentcount={this.count}></my-component>
</Host>;
}

Funny thing, you will not use class to define such in React but rather className .

return(<>{renderLabel()}<MyComponentcount={props.count}></MyComponent>
</>
);functionrenderLabel(){constmyClass=props.count>1?'danger':undefined;return<divclassName={myClass}>{props.count}</div>
}

Loops

Is it in or of or let , what’s the proper syntax of *ngFor? It has always been for me one of these things for which I have to think twice before being sure, what’s the proper way of writing it 😅.

<p*ngFor="let value of values;">{{value}}</p>

While with JSX, there is not particular templating structure to remember, but rather how to use the array map() function.

Moreover, it is also worth to notice that if your list or collection aims to be dynamic, it is mandatory to add a key attribute to each entries to preserve the ordering.

Regardless if Stencil or React, beside the object reference this for Stencil, the code is the same.

render(){returnthis.values.map((entry,index)=>{return<pkey={index}>{entry}</p>
});}

Content Projection

Even though it is maybe not something you probably use, or at least I use, every day, it is possible to pass content to an Angular component using ng-content .

<ng-contentselect="[start]"></ng-content><ng-contentselect="[end]"></ng-content>

Doing so, the component becomes more reusable as it is possible to use it with some more flexibility regarding its content.

<app-my-component><h1start>Hello</h1><h2end>World</h2></app-my-component>

To the contrary, content projection is something you may, or at least I may, use really often with Stencil because slot are a core features of the Web Components.

render(){return<Host><slotname="start"></slot>
<slotname="end"></slot>
</Host>;
}

They can be use “anywhere”, with or without any modern frontend framework. That’s why they, Web Components, really shine to my eyes.

<my-component><h1slot="start">Hello</h1>
<h2slot="end">World</h2>
</my-component>

In React, a common way to solve composition is done through the use of the default special children prop which would allow you to pass multiple children to a component.

importReact,{ReactNode}from'react';interfaceMyProps{children:ReactNode;}constMyComponent:React.FC<MyProps>=(props:MyProps)=>{return<>{props.children}</>;
};exportdefaultMyComponent;

Events

Finally, the Angular syntax expect parenthesis as declarations for events interactions.

<button(click)="inc()">Increment</button>

JSX is to some extension closer to vanilla JavaScript as the events are prefixed with the on keyword followed by a function’s call. Note again, that beside this , the syntax is the same for both Stencil and React.

render(){return<buttononClick={()=>this.inc()}>Increment</button>
}

Summary

My two blog posts were really light introductions to JSX and I am pretty sure that you are eager to discover more technical details about it. That’s why I would say, give either Stencil or React a try, or even better, give them both a try as they both use the JSX syntax. It makes kind of accessible the switch from one to the other without too much pain.

Stay home, stay safe!

David

Cover photo by Luyi Yang on Unsplash

Create Your Own NPM Cli

$
0
0

I share one trick a day until (probably not) the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Fourteen days left until hopefully better days.

If you rather like to develop your slides with our developer kit rather than with our online editor for presentations, there is a good change that you are going to begin your journey by running the command npm init deckdeckgo in your terminal.

In this article I share how to create a npm CLI command for your project from scratch.

Note that this article as our Cli is more than inspired by the amazing Stencil’s Cli.

NPM Init

Many web projects are offering a Cli to ease the creation of new projects. Using these, without any other prior installation rather than Node and npm, we are able to run command in our terminal to start new fresh project. As for example npm init stencil to create a component or app with Stencil or npm init react-app to start a new React application.

This is possible thanks to npm and their Cli commands support.

In order to create such tool, we have to create and publish a project, our Cli itself, which contains an index.js and execute a main function when called. But more important, what’s really crucial, is the naming of the project. Indeed it has to be prefixed with create- in order to be later on resolved by your command line.

For example, our project’s name is DeckDeckGo, therefore the related Cli project’s name is create-deckdeckgo . Doing so, each time someone runs npm init deckdeckgo in his/her terminal, npm performs a lookup for a related create- project and if found, download it locally and runs the main function.

Create A New Cli Project

Let’s try to create our own CLI called “Hello”.

As explained above, the project’s Cli name has to be prefixed with create- that’s why we create a new folder create-hello .

mkdir create-hello &&cd create-hello

We then define a package.json which, furthermore than defining the entry point index.js for the bin, set up the scripts and dependencies in order be able to develop and build our project with Rollup and TypeScript.

{"name":"create-hello","version":"1.0.0","main":"dist/index.js","scripts":{"start":"node dist/index.js","build.tsc":"tsc","build.bundle":"rollup -c","minify":"terser --compress --mangle --toplevel --output dist/index.js -- dist/index.js","build":"npm run build.tsc && npm run build.bundle && npm run minify","build.dev":"npm run build.tsc && npm run build.bundle","dev":"npm run build.dev && npm start","version":"npm build"},"files":["dist/index.js"],"bin":{"create-hello":"dist/index.js"},"devDependencies":{"rollup":"^2.3.3","rollup-plugin-commonjs":"^10.1.0","rollup-plugin-json":"^4.0.0","rollup-plugin-node-resolve":"^5.2.0","terser":"^4.6.10","tslint":"^6.1.1","tslint-ionic-rules":"0.0.21","typescript":"^3.8.3"},"dependencies":{}}

Using TypeScript means defining a tsconfig.json :

{"compilerOptions":{"moduleResolution":"node","target":"es2015","allowJs":true,"module":"es2015","lib":["es2015"],"strict":true,"noEmitOnError":false,"sourceMap":false,"declaration":false,"allowSyntheticDefaultImports":true,"experimentalDecorators":true,"emitDecoratorMetadata":true,"outDir":"dist/src","strictNullChecks":false},"files":["src/index.ts"]}

And some linter rules:

{"extends":"tslint-ionic-rules","rules":{"no-conditional-assignment":false,"no-non-null-assertion":false,"no-unnecessary-type-assertion":false,"prefer-for-of":false,"no-import-side-effect":false,"ordered-imports":[true,{"named-imports-order":"lowercase-last"}]}}

Finally we also need to setup our Rollup build, notably in order to be able to run commands which interact with the file system. Not the goal from this article but might be useful if we would like to create a real Cli which has for goal to create new fresh local projects.

importresolvefrom'rollup-plugin-node-resolve';importcommonjsfrom'rollup-plugin-commonjs';importjsonfrom'rollup-plugin-json';exportdefault{input:'dist/src/index.js',output:{file:'dist/index.js',format:'cjs',strict:false,banner:'#! /usr/bin/env node\n',},plugins:[resolve(),json(),commonjs({include:'node_modules/**'})],external:['child_process','fs','path','os','https','readline','zlib','events','stream','util','buffer']};

Code Your Cli

Everything is in place we can now develop our Cli. As staten above, nothing more than an index entry with a main function, therefore, let’s create a new file src/index.ts which does nothing more than printing out “Hello World”.

asyncfunctionrun(){console.log('Hello World');}run();

Once the dependencies ( npm install ) installed, we should be able to build and run the project.

npm run build && npm run start

If everything works as expected, you should notice a “Hello World” printed out in your terminal.

Publish Your Cli

Even if it does nothing much yet, we are actually already able to publish our Cli to npm ( npm publish ). If we would do so and once successfully published, everyone everywhere running npm init hello would then be able to print out “Hello World” in his/her terminal 😉.

Going Further

Here’s a couple of things you might found interesting if you plan to develop your own Cli “for real”.

Arguments

We might want to listen to some arguments ( args ). Commonly, we might be looking to print out some information if the user pass the arguments --help .

functionrun(){constargs=process.argv.slice(2);consthelp=args.indexOf('--help')>=0||args.indexOf('-h')>=0;if(help){console.log('Run your command without arguments.');return;}console.log('Hello World');}run();

Which we can test while running the command line npm run build && npm run start -- --help . Note that the double -- are only needed as we are trying out locally our bundle.

Colors

Life without colors is sad 😥. Let’s use Colorette ( npm install colorette --save ) to brighten our “Hello World”.

import{magenta}from'colorette';functionrun(){console.log(magenta('Hello World'));}run();

Have a look to this beautiful magenta color, isn’t that more user friendly happy?

Interactive Command Line

Moreover than arguments, we might want to ask the user some questions or give him/her some options while executing our Cli. For that purpose I like to use inquirer ( npm install inquirer --save and npm install @types/inquirer --save-dev ).

import{cyan,magenta}from'colorette';functionrun(){console.log(magenta('Hello World'));constinquirerHappy=require('inquirer');constquestionHappy=[{type:'confirm',name:'happy',message:'Are you happy today?',default:true}];constanswer=awaitinquirerHappy.prompt(questionHappy);console.log(cyan(`You are${answer.happy?'':' not'} happy today.`));}run();

Obviously if I run the above I will answer yes 😁.

Effectively Creating A Project

If you are looking to create your own Cli there is a chance that your goal is to create new projects locally, that’s why I will just point out my approach of the problem. To me the Cli can solve this with the following steps:

functioncreateProject(){downloadFromGitHub();installDependencies();updateLocalValues();}

One, it fetches a project from GitHub or other repo. It can be a git clone or a cURL or any other commands. The idea is to get the source from a repo and to create a copy locally.

Then, if the project is a npm one, we might want to install the dependencies. Not that in such case it is important to trace the commands and to be sure to perform a cleanUp at the end of the all process.

Finally, as we copy or clone a projects, we might want to update some variables we might have asked before. Typically, with our Cli, we ask you what’s the name of your presentation, or what’s your name? These information are replaced automatically.

To process all these steps, checkout or clone our repo.

Summary

I find really cool, even if your project is a pet project like ours, to be able to create a Cli. Hopefully this blog post will help you create yours and if you have to prepare some slides, give a try to DeckDeckgo👉 npm init deckdeckgo🤗.

Stay home, stay safe

David

Cover photo by Michele Feola on Unsplash


Third Party Service Providers. Be transparent to each other!

$
0
0

I share one trick a day until (probably not) the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Thirteen days left until hopefully better days.

I don’t know if you share my observation but I feel like currently we have reached a point in the Coronavirus crisis where every third news is either about another Zoom data breach or about a new solution or application which aims to solve and improve the current situation with the help of “anonymized cell phone geolocation data” or other behavioral data.

From a personal point of view, moreover than making me aware than once again I should pay attention to these subjects, these news also reminded me something small we had put in place in DeckDeckGo, our open source editor for presentations.

Indeed, like most of the web platforms, we do have a privacy policy and terms of services, like some we are also open source and all our code is available publicly on GitHub but what we do have and, to my knowledge few also have, is a dedicated page where we summarize transparently all the third party service providers we are using to provide our solution.

It is maybe not much, but such a page is to our eyes really worthy. No long pages to scroll, no hidden information, no tricks, no b*ullshit (excuse my french), just one page in which you can find quickly what services and what partners are used, period.

It might not speak to everybody, I'm agree, and I don't except neither big companies to do the same as well, but if just one person would read this article, maybe implement it too or improve it with any feedback, I think that would be already a small step towards a more transparent web and that's why I am sharing the idea with you today.

Here is the copy of what we are currently using respectively what we are displaying in such a page (for later reader, today is the 6th April 2020):

Services

We aim to be transparent, therefore, furthermore than open sourcing our all code on Github, here are the list of services we are using to provide DeckDeckGo.

Amazon

We use AWS Lambda, RDS, S3 and SQS to save and publish online the presentations as Progressive Web Apps. The choice behind this is mostly the fact that we thought that the S3 solution was a good one for our purpose but beside that, it was also challenging to run Haskell Webapps on AWS Lambda.

Google

We are using Firestore to save your data and the presentations you are editing. We are also using Google Firebase Hosting and Authentication. Both feature are good match to serve and deploy easily Progressive Web Apps. Their Authentication is also interesting as it provides the social login we were looking for (like Email and Github).

Tenor and Unsplash

To provide a user friendly gifs and stock photos integration we have integrated the APIs provided by Tenor, which is owned by Google, and Unsplash.

Font Awesome

The shapes, which could be integrated in your presentation, are free icons provided by Font Awesome. We do not use any APIs to fetch these respectively we are hosting them.

Mailchimp

In order to send time to time newsletters, mostly when we are releasing new features, we are using Mailchimp. Upon creating an account users are opted into it but they can opt out through their account’s “Settings” page and at the link of the footer in any of these non-administrativ emails.

All these services are covered in our Privacy Policy and Terms of Services.

Summary

Do not misunderstand me, we are using third party services, we always apply a “privacy per default” approach but we are far away of being perfect, but at least, we always try our best and we are transparent.

Stay home, stay safe

David

Cover photo by Elijah O’Donnell on Unsplash

React And Web Workers

$
0
0

I share one trick a day until (probably not) the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Twelve days left until hopefully better days.

I recently published Tie Tracker, a simple, open source and free time tracking app ⏱.

Among its features, the full offline mode was particularly interesting to develop. From an architectural point of view, I had to find a solution to compute, for statistical or exportation purposes, the many entries the users are potentially able to record without blocking the user interface.

That’s why I had the idea to solve my problem with the help of the Web Workers API.

The app is developed with Ionic + React, therefore let me share with you my recipe 😉.

Simulate A Blocked User Interface

Before trying Web Workers out, let’s first try to develop a small application which contains an action which actually block the user interface.

In the following component, we are handling two states, two counters. One of these is incremented on each button click while the other call a function incApple() which loops for a while and therefore block the user interaction.

import{IonContent,IonPage,IonLabel,IonButton}from'@ionic/react';importReact,{useState}from'react';import{RouteComponentProps}from'react-router';import'./Page.css';constPage:React.FC<RouteComponentProps<{name:string;}>>=({match})=>{const[countTomato,setCountTomato]=useState<number>(0);const[countApple,setCountApple]=useState<number>(0);functionincApple(){conststart=Date.now();while(Date.now()<start+5000){}setCountApple(countApple+1);}return(<IonPage><IonContentclassName="ion-padding"><IonLabel>Tomato:{countTomato}|Apple:{countApple}</IonLabel>
<divclassName="ion-padding-top"><IonButtononClick={()=>setCountTomato(countTomato+1)}color="primary">Tomato</IonButton>
<IonButtononClick={()=>incApple()}color="secondary">Apple</IonButton>
</div>
</IonContent>
</IonPage>
);};exportdefaultPage;

As you can notice in the following animated Gif, as soon as I start the “Apple counter”, the user interaction on the “Tomato counter” have no effects anymore, do not trigger any new component rendering, as the function is currently blocking the JavaScript thread.

Defer Work With Web Workers

Having the above example in mind, let’s try out Web Workers in order to defer our “Apple counter” function.

Web Workers

To easiest way to add a Web Worker to your application is to ship it as an asset. In the case of my Ionic React application, these find place in the directory public , that’s we create a new file ./public/workers/apple.js .

Before explaining the flow of the following code, two things are important to notice:

  1. The application and the Web Workers are two separate things. They don’t share states, they don’t share libraries, they are separate and can communicate between them through messages only.

  2. Web Workers do not have access to the GUI, to the document, to the window.

If you are familiar with Firebase, you can kind of understand, to some extent, the Web Worker as your own private, not Cloud, but local functions.

The entry point of our web worker is onmessage which is basically a listener to call triggered from our application. In the function we are registering, we are checking if a corresponding msg is provided, this let us use a web worker for many purposes, and are also amending the current counter value before running the same function incApple() as before. Finally, instead of updating the state directly, we are returning the value to the application through a postMessage .

self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='incApple'){constnewCounter=incApple($event.data.countApple);self.postMessage(newCounter);}};functionincApple(countApple){conststart=Date.now();while(Date.now()<start+5000){}returncountApple+1;}

Interacting With The Web Workers

To interact with the web worker, we first need to add a reference point to our component.

constappleWorker:Worker=newWorker('./workers/apple.js');

Because we are communicating with the use of messages, we should then register a listener which would take care of updating the counter state when the web worker emits a result.

useEffect(()=>{appleWorker.onmessage=($event:MessageEvent)=>{if($event&&$event.data){setCountApple($event.data);}};},[appleWorker]);

Finally we update our function incApple() to call the web worker.

functionincApple(){appleWorker.postMessage({msg:'incApple',countApple:countApple});}

Tada, that’s it 🎉. You should now be able to interact with the GUI even if the “blocker code is running”. As you can notice in the following animated Gif, I am still able to increment my tomato counter even if the blocking loops is performed by the web worker.

The component altogether in case you would need it:

import{IonContent,IonPage,IonLabel,IonButton}from'@ionic/react';importReact,{useEffect,useState}from'react';import{RouteComponentProps}from'react-router';import'./Page.css';constPage:React.FC<RouteComponentProps<{name:string;}>>=({match})=>{const[countTomato,setCountTomato]=useState<number>(0);const[countApple,setCountApple]=useState<number>(0);constappleWorker:Worker=newWorker('./workers/apple.js');useEffect(()=>{appleWorker.onmessage=($event:MessageEvent)=>{if($event&&$event.data){setCountApple($event.data);}};},[appleWorker]);functionincApple(){appleWorker.postMessage({msg:'incApple',countApple:countApple});}return(<IonPage><IonContentclassName="ion-padding"><IonLabel>Tomato:{countTomato}|Apple:{countApple}</IonLabel>
<divclassName="ion-padding-top"><IonButtononClick={()=>setCountTomato(countTomato+1)}color="primary">Tomato</IonButton>
<IonButtononClick={()=>incApple()}color="secondary">Apple</IonButton>
</div>
</IonContent>
</IonPage>
);};exportdefaultPage;

Summary

Web Workers is really an interesting concept. Tie Tracker let me experiment them and I am definitely going to use them again in future projects. Its code is open source and available on GitHub. If you have any feedback and even better, are interested to contribute, send me your best Pull Requests, that would be awesome 😎.

Stay home, stay safe!

David

Cover photo by Tobias Tullius on Unsplash

Angular Testing: Mock Private Functions

$
0
0

I share one trick a day until (probably not) the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Eleven days left until hopefully better days.

This week I made several progress in one of my client’s project and had therefore to write new test cases. For one of these, I notably had to mock a private function using Jest.

When I was replicating this test for the purpose of this blog post, I figured out that I was actually using Jasmine as it is the default test suite used when creating new Ionic Angular applications 😁.

That’s why I am sharing today both solutions or how to mock a private function with Jasmine or Jest 😇.

Credits

This blog post Jest’s solution has been provided by Brian Adams on Stackoverflow. The Jasmine one was inspired by the answer of jurl on the same platform too.

Kudos to them, not all heroes wear capes!

Test Setup

Once again, I am using my favorite API for the demo purpose: the free DOG Api.

Let’s then agree that our goal is to create a test for the following service which does not do much beside fetching a random dog but note that I explicitly marked the query function as a private method for demonstration purpose.

import{Injectable}from'@angular/core';import{HttpClient}from'@angular/common/http';interfaceDogResponse{message:string;status:string;}@Injectable({providedIn:'root'})exportclassDoggosService{constructor(privatehttpClient:HttpClient){}asyncfindDoggo():Promise<string|null>{constresponse:DogResponse=awaitthis.searchDoggos();if(!response){returnnull;}returnresponse.message;}privatesearchDoggos():Promise<DogResponse>{consturl='https://dog.ceo/api/breeds/image/random';returnthis.httpClient.get<DogResponse>(url).toPromise();}}

Failing Unit Test

Before trying to mock our private function, I thought that writing a test which fails would be a good start.

import{TestBed}from'@angular/core/testing';import{HttpClientTestingModule,HttpTestingController}from'@angular/common/http/testing';import{DoggosService}from'./doggos.service';describe('DoggosService',()=>{lethttpTestingController:HttpTestingController;letservice:DoggosService;beforeEach(()=>{TestBed.configureTestingModule({imports:[HttpClientTestingModule]});httpTestingController=TestBed.get(HttpTestingController);service=TestBed.get(DoggosService);});it('should be created',()=>{expect(service).toBeTruthy();});it('should fetch a doggo',async()=>{constmockUrl='https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg';constdata:string|null=awaitservice.findDoggo();expect(data).not.toBe(null);expect(data).toEqual(mockUrl);});});

Because we are performing an HTTP request and are not mocking it, the test fails on a timeout. That’s why our goal will be to solve this issue while mocking the private function which takes care of performing the request.

Mock A Private Function With Jasmine

To mock a private function with Jasmine, we can spy on our service private function searchDoggos and use a fake callback, callFake , to provide the mocked data as return when needed. Moreover, we can also test that our function has effectively been executed.

it('should fetch a doggo',async()=>{constmockUrl='https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg';consthandleSpy=spyOn(DoggosService.prototypeasany,'searchDoggos');handleSpy.and.callFake(()=>{returnnewPromise((resolve)=>{resolve({message:mockUrl,status:'success'});});});constdata:string|null=awaitservice.findDoggo();expect(data).not.toBe(null);expect(data).toEqual(mockUrl);expect(handleSpy).toHaveBeenCalled();});

Thanks to these changes, we are now able to run our test with success 🥳.

Mock A Private Function With Jest

The Jest solution follow the same logic as the above one except that we take advantages of the mockImplementation method to mock the private function.

it('should fetch a doggo',async()=>{constmockUrl='https://images.dog.ceo/breeds/setter-irish/n02100877_1965.jpg';consthandleSpy=jest.spyOn(DoggosService.prototypeasany,'searchDoggos');handleSpy.mockImplementation(()=>{returnnewPromise(resolve=>resolve({message:mockUrl,status:'success'}));});constdata:string|null=awaitservice.findDoggo();expect(data).not.toBe(null);expect(data).toEqual(mockUrl);expect(handleSpy).toHaveBeenCalled();});

Summary

Even though it looks really trivial once summarized, it took me a bit of time to find these solutions and I am really grateful that both Brian and jurl posted their answers on Stackoverflow. Hopefully this might help someone some day too!

Stay home, stay safe!

David

Cover photo by Overture Creations on Unsplash

React, Web Workers and IndexedDB

$
0
0

I share one trick a day until the original scheduled date of the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Ten days left until this first milestone. Hopefully better days are ahead.

In a previous blog post of this series I shared my solution to make React and Web Workers interacts. A tricks I experimented while developing Tie Tracker, a simple, open source and free time tracking app ⏱.

Another, I hope, interesting features of such construct and this app was the idea of handling data in the threads using IndexedDB.

The idea was simple: letting the user enter and modify data in the database on the application side (JavaScript mono thread), because such operation takes few time, but to defer every calculations or statistics to the Web Workers in order to not block the user interface and interaction.

That is why I am sharing with you this recipe in this follow-up article 😁.

idb-keyval

I am a bit picky when it comes to third party libraries because I am a bit “bundlephobic” but when it comes to interacting with the IndexedDB there are no hesitations, idb-keyval from Jake Archibald is my go-to-library.

Less than 600 bytes, tree-shaking friendly, promises based ... stop right there, I am all in!

Therefore of course in this solution we are going to use it 😉.

npm i idb-keyval --save

User Interface

In the previous post we had a “Tomato and Apple counters”. I propose that we now concentrate our self on the “Tomato” one and that we try to defer the calculation of the sum of the counter to the Web Worker.

Before any interaction with IndexedDB, our modified component’s code looks like the following.

import{IonContent,IonPage,IonLabel,IonButton}from'@ionic/react';importReact,{useEffect,useState}from'react';import{RouteComponentProps}from'react-router';import'./Page.css';constPage:React.FC<RouteComponentProps<{name:string;}>>=({match})=>{const[countTomato,setCountTomato]=useState<number>(0);const[sumTomato,setSumApple]=useState<number>(0);consttomatoWorker:Worker=newWorker('./workers/tomato.js');useEffect(()=>{tomatoWorker.onmessage=($event:MessageEvent)=>{if($event&&$event.data){setSumApple($event.data);}};},[tomatoWorker]);functiondoSumTomato(){tomatoWorker.postMessage({msg:'sumTomato'});}return(<IonPage><IonContentclassName="ion-padding"><IonLabel>Tomato:{countTomato}|Sum:{sumTomato}</IonLabel>
<divclassName="ion-padding-top"><IonButtononClick={()=>setCountTomato(countTomato+1)}color="primary">Tomato</IonButton>
<IonButtononClick={()=>doSumTomato()}color="secondary">Sumnow!</IonButton>
</div>
</IonContent>
</IonPage>
);};exportdefaultPage;

Of course as we do not have yet implemented the sum part, the Web Worker, it does not do much.

User Interaction

Our goal is to write a data in the IndexDB on user interaction, that’s why for demo purpose and for fun, I suggest that we generate a new entry in the database each time the tomato counter is incremented. For this purpose, we register a new useEffect to set entries.

import{set}from'idb-keyval';useEffect(()=>{incTomato();},[countTomato]);asyncfunctionincTomato(){if(countTomato>0){awaitset(`tomato${countTomato}`,countTomato);}}

And that’s already it. Every time the counter is incremented, the effect is triggered and in extension we are using idb-keyval to add a value in the database.

Web Workers

For this tutorial I created a new worker file ./public/workers/tomato.js which before any IndexDB interaction looks like the following.

self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='sumTomato'){constsum=awaitsumTomato();self.postMessage(sum);}};asyncfunctionsumTomato(){// TODO sum tomatoreturn0;}

We have now to access our data in IndexedDB. To solve this problem we have two options, either code everything or use a library. As a big fan of idb-keyval, I would like to use it here too.

Unfortunately, our Web Workers are shipped as assets and therefore don’t have access to our bundle and its dependencies. That’s why we have to perform a setup and import a script in our workers with the help of importScripts .

I’m guessing that one nice way would be to handle this dependency with Rollup or Webpack, probably through plugins, but I have to say I did not followed that path.

That’s why we have two options remaining. Either link an external script or download it, place it in the same folder and reference it locally.

If you would like to follow the “local way”, your import would look like the following:

importScripts('./idb-keyval-iife.min.js');

Or, as we are going to do, here’s how we can import it using Unpkg.

importScripts('https://unpkg.com/idb-keyval@latest/dist/idb-keyval-iife.min.js');

All set, we can now enjoy idb-keyval and access our data in the IndexedDB from our Web Worker too. As for example, we can list the keys() present on the database, iterate on these to get(key) their values and calculate a pseudo sum.

importScripts('https://unpkg.com/idb-keyval@latest/dist/idb-keyval-iife.min.js');self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='sumTomato'){constsum=awaitsumTomato();self.postMessage(sum);}};asyncfunctionsumTomato(){constkeys=awaitidbKeyval.keys();letsum=0;for(constkeyofkeys){constvalue=awaitidbKeyval.get(key);sum+=value;}returnsum;}

And voilà 🎉. We are using IndexedDB in all our threads 😃.

Summary

The web is so much fun.

Stay home, stay safe.

David

Cover photo by Pawan Kawan on Unsplash

React, Web Workers, IndexedDB and ExcelJS

$
0
0

I share one trick a day until the original scheduled date of the end of the COVID-19 quarantine in Switzerland, April 19th 2020. Nine days left until this first milestone. Hopefully better days are ahead.

In previous blog posts, I shared how React and Web Workers can interact and how they can use data stored in IndexedDB.

I learned these tricks when I was developing Tie Tracker, a simple, open source and free time tracking app ⏱.

In this application, I use such features to notably generate Excel spreadsheet containing the user’s entries.

User Interface

Regarding previous user interface we have developed in the series, we are still going to stick to a “Tomato counter”. Only changes we apply regarding user interaction is the fact that instead of calling a "sum function", we are going to call our Web Worker in order to generate an Excel spreadsheet.

import{IonContent,IonPage,IonLabel,IonButton}from'@ionic/react';importReact,{useEffect,useState}from'react';import{RouteComponentProps}from'react-router';import'./Page.css';import{set}from'idb-keyval';constPage:React.FC<RouteComponentProps<{name:string;}>>=({match})=>{const[countTomato,setCountTomato]=useState<number>(0);constexportWorker:Worker=newWorker('./workers/export.js');useEffect(()=>{exportWorker.onmessage=($event:MessageEvent)=>{if($event&&$event.data){download($event.data);}};},[exportWorker]);useEffect(()=>{incTomato();},[countTomato]);asyncfunctionincTomato(){if(countTomato>0){awaitset(`tomato${countTomato}`,countTomato);}}functiondoExportToExcel(){exportWorker.postMessage({msg:'export'});}functiondownload(blob:Blob){// TODO: See last chapter}return(<IonPage><IonContentclassName="ion-padding"><IonLabel>Tomato:{countTomato}</IonLabel>
<divclassName="ion-padding-top"><IonButtononClick={()=>setCountTomato(countTomato+1)}color="primary">Tomato</IonButton>
<IonButtononClick={()=>doExportToExcel()}color="secondary">Export</IonButton>
</div>
</IonContent>
</IonPage>
);};exportdefaultPage;

At this point it does not do much because the Web Worker to handle the “export” action is not yet ready and we are also not yet using its potential result, but, it should looks like the following.

Web Worker

Before implementing anything, we create a new almost empty Web Worker ./public/workers/export.js which only takes care of handling a message “export”, the one we pass to start the process in this separate thread.

self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='export'){constdata=awaitgenerateExcel();self.postMessage(data);}};asyncfunctiongenerateExcel(){returnnull;}

Afterwards, we add idb-keyval, my favorite library to interact with IndexedDB, and only call the function to list the keys() present in the database, keys which are generated by our above component on user interaction respectively each time the user increment the “tomato counter”.

importScripts('https://unpkg.com/idb-keyval@latest/dist/idb-keyval-iife.min.js');self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='export'){constdata=awaitgenerateExcel();self.postMessage(data);}};asyncfunctiongenerateExcel(){constkeys=awaitidbKeyval.keys();returnnull;}

ExcelJS

There are a couple of libraries which help read, write and manipulate spreadsheet data. I selected ExcelJS and I think it did the job pretty well, that’s why we are using it in this tutorial too.

As previously for idb-keyval, we can import the dependency in our worker from Unpkg.

In our function to generate our spreadsheet, we create a new ExcelJS.Workbook object and define some attributes.

An Excel file can contain multiple sheets, that why we then create such working page.

We skip, until next step, the preparation of the data themselves and we are implementing the generation of the spreadsheet. For such purpose, ExcelJS exposes a function writeToBuffer to generate the data to, well, a buffer which we are using to generate finally a blob (which will ultimately contains our spreadsheet).

importScripts('https://unpkg.com/idb-keyval@latest/dist/idb-keyval-iife.min.js');importScripts('https://unpkg.com/exceljs@latest/dist/exceljs.min.js');self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='export'){constdata=awaitgenerateExcel();self.postMessage(data);}};asyncfunctiongenerateExcel(){constworkbook=newExcelJS.Workbook();workbook.creator='Tomato';workbook.lastModifiedBy='Tomato';workbook.created=newDate();workbook.modified=newDate();// Force workbook calculation on loadworkbook.calcProperties.fullCalcOnLoad=true;constworksheet=workbook.addWorksheet('Tomato page 1',{properties:{tabColor:{argb:'#FF0000'}},pageSetup:{paperSize:9,orientation:'landscape'}});// TODO Prepare the data tableconstbuf=awaitworkbook.xlsx.writeBuffer();returnnewBlob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});}

We are going to add a table to our spreadsheet.

To do so, we have to provide an array which should contain an array for each line to print 🤔.

Let’s say for example that our data to display are displayed with five rows of two columns, this means that the overall array should contain five elements and each of them should contain two.

Still confuse? I guess the code will speak for itself 😅.

constkeys=awaitidbKeyval.keys();constdata=[];for(constkeyofkeys){constvalue=awaitidbKeyval.get(key);data.push([`Counter ${value}`,value]);}

The data being prepared, we can finally add the table. It contains a name, a page reference, some options, the definitions of the columns and their options and finally the data we just prepared above.

worksheet.addTable({name:'Tomatoes',ref:'A1',headerRow:true,totalsRow:true,style:{theme:'TableStyleLight1',showRowStripes:true},columns:[{name:'Label',filterButton:true,totalsRowLabel:''},{name:'Count',totalsRowFunction:'sum'}],rows:data,});

That’s it, our worker is ready. Altogether it looks like the following:

importScripts('https://unpkg.com/idb-keyval@latest/dist/idb-keyval-iife.min.js');importScripts('https://unpkg.com/exceljs@latest/dist/exceljs.min.js');self.onmessage=async($event)=>{if($event&&$event.data&&$event.data.msg==='export'){constdata=awaitgenerateExcel();self.postMessage(data);}};asyncfunctiongenerateExcel(){constworkbook=newExcelJS.Workbook();workbook.creator='Tomato';workbook.lastModifiedBy='Tomato';workbook.created=newDate();workbook.modified=newDate();// Force workbook calculation on loadworkbook.calcProperties.fullCalcOnLoad=true;constworksheet=workbook.addWorksheet('Tomato page 1',{properties:{tabColor:{argb:'#FF0000'}},pageSetup:{paperSize:9,orientation:'landscape'}});constkeys=awaitidbKeyval.keys();constdata=[];for(constkeyofkeys){constvalue=awaitidbKeyval.get(key);data.push([`Counter ${value}`,value]);}worksheet.addTable({name:'Tomatoes',ref:'A1',headerRow:true,totalsRow:true,style:{theme:'TableStyleLight1',showRowStripes:true},columns:[{name:'Label',filterButton:true,totalsRowLabel:''},{name:'Count',totalsRowFunction:'sum'}],rows:data,});constbuf=awaitworkbook.xlsx.writeBuffer();returnnewBlob([buf],{type:'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});}

Download

If you try our solution, you may notice at this point that nothing is still happening and you are right, nothing is happening from a user perspective.

Even though we have implemented the user interaction, chained both Web Worker and spreadsheet generation, we are not yet interpreting the result, the buffer we have transformed to a blob.

That’s why the last piece of this implementation is the function download() we did not have so far implemented in our component.

Credits to the followings go to kol with his answer on Stackoverflow. Not all heroes wear capes 🙏.

The function is adding an hidden link to the DOM which contains our blob, our spreadsheet, as target. Within the same function, we are calling it to trigger the download and are removing the element from the DOM afterwards.

Note that the browser will notice that it has to handle the URL in such way as we have created a blob with the specific type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet .

functiondownload(blob:Blob){consta:HTMLAnchorElement=document.createElement('a');a.style.display='none';document.body.appendChild(a);consturl:string=window.URL.createObjectURL(blob);a.href=url;a.download='tomato.xlsx';a.click();window.URL.revokeObjectURL(url);if(a&&a.parentElement){a.parentElement.removeChild(a);}}

And voilà, our Excel spreadsheet generated with a Web Worker using ExcelJS is downloaded 🎉.

In case you would need it, here is the component enhanced with the download function.

import{IonContent,IonPage,IonLabel,IonButton}from'@ionic/react';importReact,{useEffect,useState}from'react';import{RouteComponentProps}from'react-router';import'./Page.css';import{set}from'idb-keyval';constPage:React.FC<RouteComponentProps<{name:string;}>>=({match})=>{const[countTomato,setCountTomato]=useState<number>(0);constexportWorker:Worker=newWorker('./workers/export.js');useEffect(()=>{exportWorker.onmessage=($event:MessageEvent)=>{if($event&&$event.data){download($event.data);}};},[exportWorker]);useEffect(()=>{incTomato();},[countTomato]);asyncfunctionincTomato(){if(countTomato>0){awaitset(`tomato${countTomato}`,countTomato);}}functiondoExportToExcel(){exportWorker.postMessage({msg:'export'});}functiondownload(blob:Blob){consta:HTMLAnchorElement=document.createElement('a');a.style.display='none';document.body.appendChild(a);consturl:string=window.URL.createObjectURL(blob);a.href=url;a.download='tomato.xlsx';a.click();window.URL.revokeObjectURL(url);if(a&&a.parentElement){a.parentElement.removeChild(a);}}return(<IonPage><IonContentclassName="ion-padding"><IonLabel>Tomato:{countTomato}</IonLabel>
<divclassName="ion-padding-top"><IonButtononClick={()=>setCountTomato(countTomato+1)}color="primary">Tomato</IonButton>
<IonButtononClick={()=>doExportToExcel()}color="secondary">Export</IonButton>
</div>
</IonContent>
</IonPage>
);};exportdefaultPage;

Summary

Who would have thought that generating Excel spreadsheet can be fun 😉?

Stay home, stay safe!

David

Cover photo by Dan Gold on Unsplash

Viewing all 124 articles
Browse latest View live