Try latest Angular CLI Beta / RC, with Angular v7 and the new Ivy compiler, no install required thanks to npm and npx

There are some pretty nice features in Angular CLI 7, like user prompts offering multiple options when creating new projects. Also it comes with Angular 7 support, which itself has some nice features like performance and routing improvements, the new Ivy compiler, and support for latest TypeScript.

In this post I’ll show how to try out the new combo easily.

TL;DR

Prerequisites

Make sure you have Node 8.x or higher (run node -v to check). Then that you have NPM 6.2 or higher (run npm -v to check).

I highly recommend upgrading NPM at least to latest, by running npm install --global npm, or npm i -g npm for short.

Using the CLI Without Install

Recent versions of NPM come with a nice utility called npx. It allows us to run any CLI package without installing it. If we are inside a Node project already (we have package.json), it allows us to call any CLI tool from inside our project’s node_modules/.bin folder.

For Angular CLI, the NPM package name is @angular/cli, and it gives you the CLI command ng.
So, this is how I’d create a new project using the current version of the CLI, and run it in the browser, using ng serve -o (-o opens it in the browser).

In the first line, we downloaded @angular/cli from the NPM registry, that’s why we needed the full package name. In the last line we just used the command ng that we knew was available in sample-project/node_modules/.bin.

Note that we didn’t need to escape the -o flag in the last command.
The Angular CLI usually sets the npm start script to ng serve, so the line npx ng serve -o is the same as:

Except we didn’t need the -- bit, that makes npm send the argument to the actual CLI command instead of NPM itself.

Managing The Correct Version

If we already have the Angular CLI globally installed (via npm i -g @angular/cli), npx will use the version that’s installed by default. We can force a download of the current version by adding -p to the first line, and specifying that we want the ng command manually, like:

Another way is to include the version in the command, like:

I added ^ to ensure I get the latest 6.x.x, but it’s optional.

Instead of a version, we can use a tag (a named version), let’s see what tags are available for Angular CLI by running npm info @angular/cli dist-tags. The output might look like:

You can run npm info @angular/cli, without appending dist-tags, to learn so much more about the package, like all versions, description, license, dependencies, and more.

Now, to make sure we get the latest version of the CLI, we can use the tag instead of the version, like:

You do NOT need to do this if you never install the package globally (or if you globally remove it).
I usually do NOT install it myself.
But there’s benefit in learning about the gotchas, and about tags.

Using Prerelease Version (No Ivy Yet)

As you saw above, we don’t have many tags really. The only tag other than latest, is next.
So, to create a new Angular project using the latest beta/RC version of the Angular CLI, we can simply run:

You’ll see the fancy new project prompts, and after that, you can confirm the version that was installed by running:

npm ls shows the locally installed version of the package, and you should then see the latest beta or RC being used.

Go ahead and play with it!

Enabling Ivy compiler

There’s another flag you have to add when creating projects in order to use the Ivy compiler, which is --experimental-ivy. The command to create a new project becomes:

Some Ivy compiler guides suggest that you need to activate Ivy compilation by adding angularCompilerOptions object to your tsconfig.json file, with a enableIvy property set to true or "ngtsc" or "tsc" , but I didn’t seem to need to do any that.

To be impressed by Ivy, run:

And notice how the main .js file is only 32KB!

Upgrading Existing Projects

If you already have an Angular CLI project, using the latest stable version of Angular CLI and Angular itself, you can use ng update to try out the upcoming bits.

It would look something like:

And if you use Angular material, also add:

Note that these commands are pure Angular CLI schematics magic not NPM as most of the other stuff mentioned here.

Conclusion

That’s all you need to start playing with the newest bits, so go on and hack along!

Cheers,

Books on Communication / Conversation Skills

Quite often I get to recommend some of the books I read (or in fact, listened to, via Audible) on the soft skills topic, so, I thought I’d share the links to the audio books here, and refer people to this page in the future.

Here are the books:

They are in the order I listened to, and for such topic maybe listening will be easier than reading.

But you can start with either of them, and don’t have to listen to all 4 of course.

How to setup your Mac for web development: Terminal & app setup for .NET Core, Java (Android), Node, Go, and more…

I recently had a new Mac, and that made me revisit my terminal setup, which I’m now sharing with you.

The Terminal Itself

When I had my old MacBook in 2014, I wanted to go full experience, got iTerm2 and Oh My Zsh.

iTerm2 Vs Default Terminal

Later I learned that if I wanted to get a consistent experience, it’s easier to just use the built-in terminal.app, and it’s not that bad.

I still made some modifications to it. I found most commands work better with dark shell so I duplicaed the “Pro” theme and used it (a duplicate was just to increase font size).

I also used bash-git-prompt (via Homebrew) to get a beautiful prompt with full path and git status.

And there’s a lot more when I talk about dot files and .bashrc…

Oh My Zsh Vs Bash

I also learned that from time to time you’d hit a few subtle script differences between Oh My Zsh and the default Bash, usually arround having to quote some strings that Bash doesn’t require quoting. This made copying and pasting commands from tutorials etc a pain sometimes, so I defaulted to Bash this time.

You see the pattern here, I try to utilise the power of defaults.

Homebrew

There’re a lot more Brew apps than there are for Mac Ports. Some of you might even not have heard of the latter, so, I just use Brew.

One thing I learned over time is that if something has an installer, I’d better use that installer itself, unless the installer is really a shell script itself.

I’ll talk about each case separately below.

Terminal Dot File Tweaks: .profile, .bash_profile, and .bashrc

I got quite confused between the 3 files at first, and head to learn about all this crap about login shells and non login shells, and how Mac is not standard itself. I ended up changing my files as below:

.bash_profile

Run the terminal-type-agnoistic (forgive my lack of clever wording here) .profile file if it exists:

.profile

Just run the ~/.bashrc file if it exists

.bashrc

That’s where I end up putting all my terminal customizations. The next steps are to divide these into smaller files kept in a directory really.

For example, that’s where I enable the git prompt, which was by following their README by the way if the code feels complex:

And also show hidden files in completions:

Relative .bashrc Files!!

This is a good one, and the motivation for writing this blog post, seriously.

I like to have the latest version of everything I use, but I had a React Native project that required Node 8 (for some dependencies that are not updated yet), and Java 8 (for Android).

I decided that I’m going to install multiple versions of Java and Node (via NVM), and pre-select the correct version in my project folder using a .bashrc file.

I wanted the versions to be pre-selected for me so I have to remember to select them less often. Then came the idea:

What if the terminal could load a .bashrc file for me automatically based on directory?

I usually use VS Code integrated terminal, or use its menu to start the terminal app in the project directory, so that will work great.

But the .bashrc file will have to live outside outside of the repository, because I cannot modify the repository for everyone else or a file that gets lost with git clean -dfx, so it needs to be something like:

And I did it simply by adding the following to the ~/.bashrc file, the one in my home directory which gets loaded automatically as I explained:

Remember that I’m a true newbie when it comes to this stuff. There’s probably a better way to write this, and I probably should have capitalised bashrc_dir, and sure violated other bash conversions, but it works!

I’ll talk below about what it enabled me to do.

Bash Completions

For a long time I felt that I wasn’t getting any tab completion (auto-complete menu on pressing tab after a command) in my new setup. At first I thought it was just Bash vs Zsh, but I recently learned that I can enhance it a bit.

I installed bash-completion using Homebrew, which was not enough by itself, so I added the following to my home dir ~/.bashrc:

Now I started seeing completions. For example if I type npm run inside a folder with package.json file, and press TAB twice (not sure why twice), I see a list of npm scripts. Similarly if I’m in a git folder and type git checkout TAB TAB, I get to see all branch name (or a warning if there are so many).

Watching Files for Development

A lot of stuff, especially NodeJS stuff, watch files for changes during development. Think Webpack or TypeScript compiling your files, or React Native packager watching files to send them to mobile device app, etc.

To make this work with no errors, there’s something to do, and something NOT to do.

DO NOT install fsevents or watchman using NPM global install. This will only cause issues.

Instead, just install watchman using Homebrew:

If you still have watchman or fsevents errors. Check if you have either of them installed via NPM, remove them, remove watchman from Homebrew and install it again.

Git

I installed git via Homebrew. That way it’s pretty easy to update it. I also installed bash-git-prompt similarly as I mentioned above.

VSTS

Another tool I installed via Homebrew is git-credential-manager, which allowed me to login to Visual Studio Team Services (VSTS) git repositories in a slightly fancier way (using browser login, that’s later cached).

GUI Client

Coming from Windows, I originally cired for lack of a good alternative for Git Extensions. SourceTree was the closesth but wasn’t close enough, and GitHub for Mac was overly simplified for me, that it felt lacking.

Nowadays I’m pretty happy using Fork which I got using the official installer. It is a great git client for when I want to look as graphical log or mess with my changed files diff before committing. I still use the terminal for switching branching, pulling/fetching/pushing, etc.

Terminal Intgeration

In Fork preference, the Integration tab, you’ll find a button to install fork as a command to your terminal. This allows me to type fork . in the terminal in any folder in my git repository to open that repository in Fork.

Diff Tool

For some reason my head refuses to understand any 3-way diff tool other than Kdiff3, which used to come with Git Extensions. I still use it now, installed via brew cask.

VS Code

Since we are talking about modifying files here, I’d like to talk about how I modify them. I use VS Code, please do not hate me Vim users!

I use the Insiders edition of VS Code. This is like a daily build of VS Code. You get all new features weeks before others do, which is awesome. The Insiders edition is fairly stable in my experience, and the update is very quick on Mac too (unlike Windows), so it’s awesome.

To get the Insiders edition, I just google VSCode Insiders and click on first result.

VS Code To Terminal

The only caveat of frquently updating VS Code is: because I don’t know when I might want to restart my VS Code for update, I avoid running long running tasks in its terminal.

I can press CMD+SHIFT+P to show command palette, and type new t then select Open New Terminal to open a new tab of the default terminal app, started in the project folder. You’ll see later why this is so cool.

Terminal To VS Code

To easily open files and folders in VS Code from the terminal, I can press CMD+SHIFT+P to show command palette, and type install then select the command that installs the code-insiders command in my PATH.

Sometimes I feel that this is a long command though, completion works startingfrom code-, I probably should add just code as an alias.

Java

I wasn’t sure whether to go with installer or brew for this one. I knew I’ll have to fiddle with paths anyway, so, I thought I’d use Brew so it’ll be more transparent to me. I used brew cask to install both java (latest) and java8.

In my user ~/.bashrc file I added:

Java 8 for Android

In my project specific .bashrc, I added:

That’s a lie. I only did when I was writing this post. I made up the first two lines. My real old code looked like:

Note the jdk1.8.0_181 bit, which is whatever exact Java 8 version I got from brew cask. I replaced it in the first snippet to be dynamic, and it might be worth mentioning that I made up the JDK_ALL and JDK_8 variables.

To test that it still works, I opened my project in VS Code, and in the terminal I typed java -version, must be a single - BTW!

Installing Android Itself

After struggling a lot with Android and Homebrew on my new machine, knowing that I got it to work somehow before on my old MacBook (which was out for repair at the time of new machine setup), or did I? I decided to go with the official installer for it.

I googled for Android, downloaded Android Studio from it, and then ran it to install the actual Android SDK the first time it opened.

I later discovered issues (maybe) specific to my active project, like Java version as I mentioned, but also found myself having to set a few extra global variables to ny home ~/.bashrc file – besides project-specific Java 8 which I mentioned separately above:

All the usr/local stuff is probably from Homebrew, could have been replaced with $(brew prefix), and all ~/Library/Android/sdk is from official install.

Maybe next time I get a clean environment I’ll do it differently. I don’t know!

.NET Core

I used the official installer for .NET Core runtime and SDK as it allowed me to easily install multiple versions of the SDK, which Brew didn’t. I did have a project pinned to a specific SDK version via global.json in its repository.

I went to dot.net, clicked on Download, looked at the end of the page for download archives, checked for latest current version for my machine, and for 2.0 for my project, then found the exact version fo the SDK in the 2.0 page.

Note that SDK 2.1.2 is for .NET Core is NOT the same as SDK 2.1.200, and that both are for .NET Core 2.0 not 2.1!

Node

I installed both Node and nvm (Node Version Manager) via Homebrew.

I don’t relying on NVM too much. Things can get funny with global NPM dependencies when you switch Node versions using it, so, I keep that to a minimum. Here’s the idea:

In my home ~/.bashrc, I added the following lines:

The > /dev/null 2>&1 bit is to hide a false negative error from the console, that I get about NPM prefix. You see I try really hard to hide that error in other ways in vain!

Then, in the folder right above my project get repository, where I had another relative .bashrc file as I mentioned above, I added:

I can confirm that I have a different version of Node by opening my project VS Code workspace, and typing node -v in the terminal.

Upgrading NPM

With the above setup, it’s worth noting that I get 2 NPM installs, one that’s the global / default, and one that’s specific to my project.

Node 8 comes with a slightly outdated version of NPM, so, to update that, I opened the VS Code terminal inside my project workspace, ran node -v to confirm it’s picking it correctly, then I upgraded NPM by running npm install -g npm, and after it finished, I ran npm -v to confirm it’s picking the new version.

I did the same thing with my global Node install BTW. I told you I love to use latest versions of my stuff.

Golang (Go)

This was another silly one. I remember struggling with code paths on my old machine after installing via Homebrew, so, like Android, I just went to the official website and installed it via the official installer.

After that, I learned how that if your Go code doesn’t live under ~/go/src/project-name, you can get a lot of problems because of GOPATH environment viable. I really wanted to fix this!

That annoying problem is what prompted me to finally blog about relative .bashrc files, after posponing that for a very long time.

The following snippet, which I’m experiencing with in a project-specific .bashrc file, but can also be in the home ~/.bashrc file, fixes this problem:

Turns out you can set multiple GOPATH folders, separating them by : (or ; in Windows). I also had to set GOBIN as you see in the script comment.

RVM (Ruby Version Manager)

I don’t do Ruby really, but my current project had a script that ues it. There’s no homebrew option for RVM, and following the official instructions -which you get by googling RVM– failed on my Mac.

The error is around a pre-install command you need to run, which uses some signing / signature verification tool called gpg, which is not available on Mac.

I found this blog post which suggested another command to run before the installation:

Now I was able to follow up with the official install.

I also made sure I have the following in my home dir ~/.bashrc of course, this code is a copy-paste from instructions:

Performance And Battery

Away from terminal really, more into the menu bar, but still especially useful for me as a developer and pro user, I have a few apps to ensure I ge the best performance and battery life I can from my Mac

Macs Fan Control

MacsFanControl is an almost-necessary free app to prevent my i9 MacBook 2018 pro from throttling while I’m doing heavy work.

Turbo Boost Switcher Pro

I bought the paid (Pro) version of Turbo Boost Switcher so that I can turn off Intel Turbo Boost automically when I’m running on batteries.

It’s a of the opposite of the previous app I know, but quite often when I’m running on battery I need it to stay alive longer. Or when I don’t, I just turn off the auto mode and explicitly turn turbo boost on, and continue working.

Battery Health

Battery Health is another paid app for the battery, but this time just for monitoring.

I like to know how my overall battery life going over the year, and this is the app for that. But I also LOVE to see an estimate time of how long it’s until I run out of battery, a feature that used to be built into macOS itself then got removed.

Touchbar Tweak

If you are like my, you probably hate a lot of things about the MacBook 2016+ keyboards, especially the touchbar, especially how the Esc key has no feedback. Yuck!

A few people thought that the best tweak for the touch bar is by giving it Haptic feedback (a vibration feel on touch). Until Apple implements that, we can use the free HapticKey app, which vibrates the touchpad when you tap the touchbar.

It’s not the real thing, but it’s close!

P.S.

Apple, if you ever see this, please in addition to haptic feedback on the entire touchbar, please also change the Esc key to the same kind of physical key you use for the power button, PLEASE!

Other Tools

I defintely missed a few more here. There are specific extensions and settings in VS Code, fixes for common Node package install issues, and more, but each feel like they should be their own blog post.

For now, I’ll mention that I also have these in my home .bashrc folder, and I don’t remember now what problem they were fixing – I should have added comments, I know!

There’s also another tool I have called bash-it. It supposedly adds some nice stuff to Bash that makes it closer to Zsh, but I haven’d noticed any specific benefit yet. They also have customization bash prompt themes, but I use bash-git-prompt as I mentioned.

And I use SnagIt for taking screenshots, and Audacity for fine-tuning audio of my YouTube videos, which I haven’t posted in a while, so, that doesn’t matter.

I also use this old tool on Github called RDM when I want to experiment with non HiDPI resolutions (which seem to increase the battery life slightly, unless I’m imagining), but the effect is not confirmed, so who case.

And if it counts, I installed Docker using the official installer, and using Docker for Mac official support for Kubernetes to install that.

Conclusion & Updates

When I try to modularize my ~/.bashrc file, and check my old Mac for anything else I had in the past that’s still useful, I might put it all on github and add it to the post. Make sure you follow me on Twitter for updates.

Cheers,

My approach to leadership even when you’re not The Team Lead, or how to minimize resistance to change in your company

Recently I have been asked a few times about how to become recognized as a leader when you are not officially appointed a Team Lead title, or even worse, when the particular team you are working on is too small, or you are even working solo on an independent piece.

Another common question I see people around me ask is: how to introduce new practises to a team that still doesn’t recognize what’s holding them back or what they are missing out on, without having too much resistance, especially from senior people. It’s interesting to note that this question is often asked out of a specific struggle, and that leadership is not usually mentioned when the question is framed.

But “how to lead” and “how to drive change” are very related

This blog post was motivated by a question I answered recently on Quora, and another one I answered in a tech group on facebook.

The Quora question is just an example of how this works, so do not mind the specifics.

How can I ask a senior developer to learn Git properly as he always commit code directly on the master branch?

The other question is what I’m sharing with you here. Let’s look at what it says:

What are your tips for developing company performance in Agile, clean code, unit testing, good software design, etc.?
Habits are usually a problem, and there’s difficulty of persuasion

Below I answer the question from my observations working as a consultant for 7+ years, and as a software developer for over 12 years.

The Answer

It depends on your role, your power, how much buy in you got from leadership, etc.
But…

This is the general framework I apply as a consultant whenever I go to a new place (usually every 4 – 14 months):

I keep my ears open all the time, looking for opportunities to help people technically etc.

I like to help, but it’s also good to have people recognize you as someone who helps and come to you directly for help after that. When they come to you for help, they develop habit of listening to what you say, and accepting it.

I ask tough questions from day 1, although only when a relevant topic comes

Questions have always been key to how I function. Asking the right question gets you a very good idea on where the team / company stand relative to what you are asking, and much more.

It tells you whether they already do things better than you thought, or otherwise, how close they are, and whether they acknowledge that they are missing something.

The question also communicates to the team what to expect you to push, what you stand behind. If you ask the question in a neutral tone, and only focus on understanding their position (via few follow up questions as time and situation permit) instead of enforcing a suggestion solution at the time, it also signals to them that you are not a perfectionist who wants to move everything upside down in their comfort zone.

Small talk, coffees, etc.

I have room for improvement here, but small talk, chatting to people about their lives, their kids, their favourite sport / movie, without being judgemental of course, really helps make you a friend.

If your time permits, join in the coffee trips and sit with them for lunch if applicable. This one is optional, but it helps deepen the “one team” feeling.

I embrace coming as honest / vulnerable, do not act like “I know it all”

Stating the obvious, but, I do make mistakes, and that’s OK. When you push for change, even by just asking innocent questions, people hold you to a higher standard, and that’s also OK. Actually it’s a good thing.

When you create a Pull Request, and other people point at a mistake you did, and you smile and fix it. What happened in here? Two things: They put on their “What’s the right thing to do” hat, and you put on the “I hold myself to the same values I ask you to stand for” hat.

This really helps in getting people to have a development mentality, and also helps them own the improvements. It’s not about you anymore, we are all peers, and we all hold each other to what we agree is correct.

It also works in non tech areas, like applying a process etc. When you make the rule become how they judge you, they stand more for it.

I look for opportunities for small wins.

A small Agile or tech practice that people can apply without much effort and can appreciate the results. There is always one if you keep your ears and eyes open, and ask the right questions. The whole point here it to get them to appreciate that change (a small change in this case, that they don’t feel a strong urge to resist) is actually beneficial, which helps when you start pushing bigger changes.

Now that I built enough credibility, I start making the case for the bigger items

This works because I’m often injected into teams as a developer and a mentor, or leading a smallish team of 3-4 developers. It’s different when I’m expected to be an advisor to the leadership.

I watch what we agree on, to decide whether to keep asking or start making statements

Asking works most of the time, but telling / making firm statements is needed too. When people say they don’t want to do something, quite often they ignore the result of their old approach vs the new approach. Your friend in these objection time is the word “Why?”.

If you keep asking “Why?” and “What will happen if…”, people will stop answering at some point, and you’ll have to “tell” them what you think the answer is, and get them to agree or make their own answer. That’s one kind of telling.

When you reach to a point where the flaw of some approach is stated, then it’s time to put on your firm face and “tell” them that we should avoid this problem using the change you are suggesting (detailing the change not using words like “my change”, you don’t want it to be them vs you), and “tell” them that unless they find a bigger flaw in it, that’s what we should follow

At the end of the day, quite often most decisions are trade-offs. You just need to agree on which trade-off has higher cost.

When you do this, you should also sense the air to see if it’s better to make your statements use a neutral tone, or sound a bit more firm, which still should not mean shouting!

Praise and recognition, lots of it, but keep it genuine

Recognition

I make sure to give people credit for everything good they do, no matter how small. I repeat it in front of more people, and in front of other teams.

Side note: When you do this, you don’t worry about others taking credit for what you did. Because when you keep giving credit to other people around you, external people and management DO see that you are a common factor in these, and while the people you praise get the credit, you too get extra credit!

This plays well with the old leadership playbooks: When talking about good things, name people in every occasion. When talking about bad things, always name facts and avoid naming people, use “we” instead of specific person’s name. If you really have to mention a name, say it neutrally and quickly.

Praise

When someone does something good, whether related to the changes you suggest or not, make sure to praise them, in public, loudly. Praise for small things. And do not make up praise.

I learned an interesting secret: when you look for good things that people do, you always find them. Even people that your might consider a negative net producer will do good things and if you look for them you’ll find them (handling negative net producers is a whole topic on its own).

People love praise. It always strengthens your relationship with them. They also feel the urge to do better. It also helps you (me in this case) find positives in the relationship with the team when you are looking for genuine reasons to praise them.

Be warned though: It’s VERY important to keep your praise genuine and about good things you found NOT made up. Why? Besides being dishonest, people will see that you are just giving them empty praise. While empty praise still works on others, it reduces your creditability, and it sure still does not work on those who resist change the most (and hence recognize they have the most conflicts with you). It’ll just lead them to disrespect you and fight you harder.

People often recognize me as someone who is approachable and respectful, which I think is mainly due to vulnerability and positivity (which praise is part of), even though I’m also someone cites concerns in the form of questions or statements when required. This leads to them describing me in my LinkedIn profile recommendations as someone who is they enjoy to work, but not afraid to speak up.

The good news is that there’s no secret about it. It’s not too hard to recognize all people for the good work they do when you are actually looking for it.

Finally

When you are already recognized as leader / coach, you don’t have to go through all these steps. You might be better off asking the existing leadership about their priorities and the work conditions, and directly start addressing the low hanging fruit from these.

Do you have any experience / tip you’d like to share on this?

Bring on your own version / perspective

Little Known Angular XSRF Protection Support In Http & HttpClient APIs (v2.0+ vs 4.3+)

This post is about a new feature / code restructure in Angular v4.3+ that is pretty well documented, yet, hardly talked about in posts, and worth drawing special attention to.

The feature we are talking about here is Angular helpers for guarding against XSRF attacks in its old and new HTTP modules.

What Is XSRF? (Cross Site Request Forgery)

XSRF (Also knwon as CSRF) is a form of web security attack that can allow others to impersonate your identify and perform some actions on websites that you are already logged in to. Let’s start with an over simplified version of how the attack and protection work (as I understand them).

XSRF Attack

  • You login to website A and get a session cookie, maybe use it a bit
  • You open website B in a new tab or even the same tab, you are still logged into website A
  • The owner of website B (or someone who hacked it) happens to be a user of website A too, and knows what calls it sends to the server to perform different tasks
  • Website B loads a hidden form with the submit URL going to website A
    . It could be sending an email on your behalf, deleting many important items, whatever that is you can perform in website A
  • Website A takes the request and performs it, because you are still logged in

XSRF Protection

There are many ways to protect against this attack. Mainly you’d use server-generated token stored in somewhere that’s not a cookie.

A classic server side application with a single form each page that submits back to the server can generate a token as a hidden field in the form, and then validate it on submit, and generate a new token for the next page etc. Website B will not have a way to generate a valid token when trying to simulate the server form.

A client side application (SPA) could do the same with headers, sending the token in a header when making any API request (A.K.A AJAX call), and getting the new token in the response of that API / AJAX call.

The initial token may be obtained via a call to a known API, or in a cookie that’s downloaded when the HTML page is loaded. Since the other site can only do only put a form on its own page, it cannot modify headers to inject the token, or call the API directly due to CORS (Cross Original Resource Sharing) policy.

Angular HTTP APIs & XSRF Protection

By default the Angular v2+ Http service (from angular/http HttpModule) had builtin support for the Cookies To Headers XSRF protection technique we just mentioned.
If it detects a cookie with the name XSRF-TOKEN, it adds an HTTP header named X-XSRF-TOKEN with the same value to the next HTTP request you make.

Since the support is so transparent, I have seen developers who did not even realise it’s there. If you are a front end developer who had someone else on the backend implement the server issuing and validation of the tokens, you may not even realise it’s there.

But things changed slightly in Angular v4.3, that you probably need to be aware of.

HttpClient

In Angular v4.3, a new library for creating HTTP (AJAX) requests was added to Angular.

Instead of injecting an Http object from @angular/http as you did since v2.0, you inject an HttpClient from @angular/common/http. The new HttpClient has several new features but most notably:

  • Automatically maps responses to JSON by default (configurable)
  • Added a pipeline for injecting middleware to requests/responses (called interceptors, like those of AngularJS v1.x)
  • Has some really cool testing utilities that are much simpler than the old v2.0 Http module

Whether you like it or not, you’ll probably end up using the new HttpClient in your Angular applications. This is because starting Angular v5.0, the old Http will be deprecated (so it may be removed entirely in v6).

For the record, the relatively quiet introduction of the new module in v4.3 and the deprecation of the old in v5 still makes me feel anxious regardless of how good the new one is. The old one generally just worked, and the way it’s replaced reminds me of the 3 router versions that were written during Angular v2.0 pre-releases.

XSRF Interceptors

Since the new HttpClient came with proper support for request and response interceptors, it made total sense for the XSRF support in it to be implemented as interceptors as wel, not as part of HttpClient itself.

This is all cool and good design, but there is a catch, which although is well documented, it’s not having enough people talking about on Medium and Twitter etc.

The XSRF in HttpClient is an opt-in feature. It’s NOT enabled by default, unlike the Http service. This means that you could easily migrate your code from Http to HttpClient and not realise that your builtin XSRF support has been lost, until you test the application and you start getting 403 Unauthorized errors from your API of course.

Luckily, all you need to do to get the support back is just import another module, like:

// import { HttpModule } from '@angular/http';
import {
  HttpClientModule, HttpClientXsrfModule
} from '@angular/common/http';
// ...

@NgModule({
  declarations: [
    AppComponent,
    // ...
  ],
  imports: [
    // Now this one is gone:
    // HttpModule,

    // New replacement
    HttpClientModule,
    // Restore Xsrf support
    HttpClientXsrfModule,
    // ...
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Gotcha

The old HttpModule supported sending the XSRF-TOKEN header for all HTTP requests. While the new HttpClientXsrfModule made sure it doesn’t send the header for GET or HEAD requests.

GET requests don’t make much sense for XSRF attacks anyway because the attack is about changing data while impersonating you (the attacker cannot get the result anyway because of CORS rules). GET requests by definition should not change data.

However, if your API relies on this, your app might break when you move to HttpClient. In that case you might want to look at this Github issue I created and apply the workaround github.com/angular/angular/issues/19885

More Goodies

The HttpClientXsrfModule also makes it easy to change the cookie & HTTP header keys separately if you require that.

Of course you have to remember that both XSRF protection and JSONP require server support as well from the API server you are calling for them to function.

Finally, the same support is also included as opt-in for JSONP requests as well, by adding HttpClientJsonpModule (also from @angular/common/http) to your imports. This utilizes the power of interceptors that allows them to avoid sending an HTTP request completely and do something completely different instead.

As I said, the usage of HttpClient in general, as well XSRF support (and what is XSRF in general) is well documented by the Angular team.
Check it out at https://angular.io/guide/http.
I still noticed that not enough people are talking about this feature and the actual change, so, hopefully this post can bring some awareness to it.

A Very Long Tale of My Career, Or How Teaching NHibernate & Other Backend Stuff Got Me Into Readify

I told a few people how I joined Readify, but I never wrote it down. Here’s more than you ever wanted to know:

Before The Beginning

I didn’t have a computer when I was a kid, until I was 14 or something. I only had access to DOS computer in the mid-to-late 90s in a community center, where I was not allowed to run Norton Commander, because “you won’t learn anything like that”. I learned DBASE III, at the time my teacher was using Windows 95 or 98.

Then I got a computer. Internet came later. I noticed I could be a web developer. PHP was the craze at the time. I jumped on it, and learned some C and Visual Basic too. I didn’t get so far with either.

Then my family moved into another house. My new neighbour who attended a Computer Science college said they had free lectures on Visual Studio 2002 beta during summer, and I couldn’t have missed them! I got the software with a help of the public library I used to go to. The IT manager there was so helpful, even though I was barely allowed in the “grownups” section at the time. That reminds me of something else that’s useless in this context, which is how for over a year I used to note “useful URLs” in a small book, until an affordable Internet Cafe was open in the same library, and I was able to surf like a guru!

My First Company

I started work when I was at uni. I attended a international Microsoft developers conference in Egypt on 2004 and was picked as a best attendee by one of the speakers, to win a Microsoft MCAD training track (8 courses). I was in my first year of 5 years engineering uni.

It wasn’t easy doing a 4 hour training 3 days a week plus uni, but it paid off. I was lucky that most students were senior developers at the time, and all somewhat new to .NET.

A friend recommended me as a trainee in her company in the summer. She left the company, and shortly after, the other 2 developers left on the same day. It was funny. I had to handle this little company myself, with an interesting technical founder who at the time only knew classic ASP. I was interviewing both junior and senior hires technically while still called a trainee!
I also wrote a PoS Windows app that sync’ed with my web app’s SQL DB via Access and DataSets!

My First Move

Then one of the 2 developers who left invited me to work as a freelancer in her company, for a month, with 6 times my full time trainee salary. It was a custom CMS written by someone who left and no one else could wrap their head around the code, and the company needed to extend it in ways it was never meant to work.

That was flattering, and I was crazy about CMSes at the time. I took it, and after a few weeks of day and night work, I decided to dedicate my day to the new work.

It was uni time again (September 2005). My work was successful and led to more work. Eventually someone else left, and he was taking care of a system that nobody wanted to touch for a very different reason. It was in VB.NET! I didn’t know VB.NET at the time, but I took it, and it had some interesting business challenges, and .. em .. KSA remote client management challenges.

I decided to continue working as a part-timer during uni. It was the beginning of crazy era.

The Work – Uni Balance

A New Life

I’d go to uni, finish and head to the company, through a very crowded road (about 2 hours long, including 45 minutes in a single crossing), finish work so late I’m too tired to go home, pull in some chairs together to sleep on, to wake up early enough before anybody comes, and go to uni.

I’d go back home every 2-3 days to change my clothes. I also would take 2-4 weeks off before and during exams. I was so proud I was so rich I could order food whenever I wanted, and was being treated just like all the “big developers”.

Officially A Full-Timer

Then the same person who brought me to the company told me about this American company opening in Egypt, that is always working with pre-release technologies. She said she failed their test because she didn’t take it seriously as the company didn’t seem to have a proper office or anything. She told me she thought she was wrong, as it’s just a different culture. She told me the UX designer I liked the most in our company went to work there.

So, right after finishing a uni final test, I went to this other company for a tech test. It was a horrible idea. I think I did too bad, but having been blogging for a while about tech events in Egypt and other tech stuff, when .NET bloggers were relatively rare at the time (talking 2006), especially in Egypt, I was a well known person to the guys who worked there. Also one of them knew me from a tech forum we both used to help people in.

So, I got to the interview with the tech founder. He’s an Indonesian from a Chinese background, who I think holds a degree from Australia, and is partnering with an American in founding a company in Egypt! He was a legend!

These maybe were some of my best times in life. I worked as a full-time junior developer for pretty much the same salary as my previous company. I worked in the same way I did in the other company plus 10 hours a day in weekends to have 40 hours total. That was doable because our main account was in the U.S., and the time difference was in my favor as I was working mostly by night during uni.

A Senior Title

I really enjoyed the company. The founder made it ever better. He was an awesome geek. He stayed late with me, came on weekends, and sometimes we both would go out to eat or just walk. That was life!

I was the only junior developer at the time. A year later they started hiring junior developers and I was promoted to a senior developer. I was proud I got the title while still a student, even though that didn’t include a pay rise. That came a few months later though.

The company also encouraged me to start public speaking. I was a semi-frequent speaker at the only .NET usergroup in Egypt at the time, and the company itself held its own conference every year, having me as a speaker twice. That was cool, and helped more people recognize me.

The “Experts” Team

After graduating (2008), a friend from the MCAD courses told me his friend left his well-paying online freelancing work to go back to their old company, which was re-opening. It was a part of a very large well known brand in Egypt. He called me to join him in an architecture team they called “The Experts Team”.

It also had the guy I liked the most from the usergroup organizer team. I was getting ready for marriage at the time, and for those who don’t know, marriage costs tens of thousands in Egypt — that’s IF you have an apartment, and I was lucky that my parents suffered from paying loans for many years to ensure I had one before I understood why that was important.

Flying To Abu Dhabi

The Decision I Never Considered

So, I moved to the same team as that friend. I got married. I was happy where I was, working with nice people in a big company. Then the same guy who got me in the company told me about a senior position in a big company in Abu Dhabi. He told me he was called for the opportunity but he felt it was small for him, but OK for me. At first, I rejected the idea. I was still new in my company and wouldn’t want to leave so quick. I also never thought about traveling, but -let me admit- mostly because I never thought I’d be able to do it.

Then someone else from the company encouraged me to take it. My father, whom I thought would say don’t go, encouraged me to go abroad in a way that was very touchy. We are talking 2009. Things weren’t that bad in Egypt, but they were still definitely not good. Having recommendations from the company I didn’t want to leave, and also because the phone interview with my team leader was so nice (He was sure a geek I’d enjoy working with), all led me to go ahead with the offer.

That was the best decision I have taken in my life. Not just because the Egyptian company closed again in a year or two (the parent company thought software was lengthy and expensive compared to integration like SharePoint etc), but also due to political issues later, etc.

Life In Abu Dhabi

I arrived to Abu Dhabi. First time to travel by air. I got freaked out in Abu Dhabi airport when they couldn’t get a scan of my “eye print” and kept me held for an hour. But I was let in! And I started work. I was well respected and had my fame with me, which was nice at first. It wasn’t an oil company but the closest to that (half owned by government). I didn’t have the best salary, but had the highest medical insurance etc.

The Real Beginning

Meligy, The Backend-Focused Speaker

At this time I was all about design patterns and framework design. I had already spoken in the usergroup about all of these. I was being followed by Scott Hanselman on Twitter!

I also went too fast with Entity Framework when it was very new and it screwed me up. I turned to NHibernate, and after joining their mailing list looking for help, I myself became someone who helps others, and someone who interferes with some of the maintainers for how they’d make fun of some of the questions etc. I made good friends with some NHibernate core members. And this was how I heard about Readify.

In February 2010, I was invited by the .NET usergroup I used to speak in, to speak in a big conference they organized, called Cairo Code Camp. It was the 2nd one and I had already spoken at the first. I couldn’t miss it. Scott Hanselman was there. I’ll see him for the first time, AND, as a speaker, both of us, WOW!

I was delivering a session called “Framework Design Guidelines”. That is, stuff inspired by MSDN Patterns and Practices, and Design Patterns and Domain Driven Design.

Someone else was in there, Mitch Denny, the Readify CTO at the time (now in Microsoft U.S.). I enjoyed a nice conversation with him during speaker dinner party, although I didn’t know where he was working at the moment.

My Friend, From The NHibernate Core Team

Now, back to Abu Dhabi. Work was getting boring. I was hoping for some challenging work in U.A.E (Abu Dhabi or Dubai), again, not thinking about travel. It wasn’t very successful. I chatted to my NHibernate core team member friend, and he pointed me to someone he knew, who blogged about his interview process at Readify.

I was pretty impressed. I thought they’ll never take me. There were so many celebrities who worked there that I just knew I won’t be good enough to work there. I tried anyway!

And guess what? They called me back! I learned that Mitch actually remembered me, and knew where he works! I still had to go through a tough interview, and I passed! Hooray!!

The Foolish Call That Made Me Decide To Join Readify

But I wasn’t sure whether I want to make such a big move myself. Fun fact: when they asked me: “which state would you be interested in going to?” I said Sydney only because it was the only city I knew its name (and didn’t remember how I knew it). I wanted to get something extra to weigh the decision.

I DM’ed Scott Hanselman. Didn’t I tell you he was following me on Twitter and knew me in person? I asked for someone who can tell me in all fairness whether Readify was a good company. He pointed me to Tatham Oddie, our current Delivery Lead today :D — He had his phone number on his blog at the time. He picked up the phone once to find an anonymous international caller saying Scott Hanselman recommended him for asking whether Readify was a good company. Of course he wouldn’t say anything bad about his company to a stranger! It was a foolish call, but it was all I needed to get going.

Making The Move

My annual-paid rent was expiring — I had to borrow 60% of my salary to pay that rent (Not having a good enough eyesight for driving made cheaper out of the city options impractical for me). I had to live in a temporary apartment that had legal issues and could have had me kicked out any time because it was cheap and had nice furniture for me and wife. The visa took from April to August, which was very unusual, but it came, and I arrived in Australia!

Since I arrived, so many things happened. I hated Sydney at first because Abu Dhabi was so fancy (I used to move around in latest-model taxi cars, for 6 Dirhams or so — the city was so small). I learned to see the beauty of Sydney only a few months later. My son spoke his first words here, and I and him call it home today.

It’s been long time since arrival in September 2010. During the time I only left Sydney to one-day company annual events in other states, and a one week holiday in Melbourne earlier this year.

Life At Readify

I joined Readify and worked in the same company as so many celebrities I used to fancy, and so many great other people I discovered while working here.

I saw the company go through several growing stages, every time feeling a worry about losing the culture I love in the company, then feeling happy that it is as awesome as ever.

It is definitely an awesome place to work. There’s countless number of great people around here, and I also more than often get to either work at great customer sites with great people I loved to go back to and help with the great work they are doing (my current client is one of them), and other sites that are not as great as I’d hope to see them yet, but instead of feeling feeling bad, I feel inspired to help them make things better bit by bit. Because that’s why you’d hire a Readifarian :)

Yesterday And Today

I got to work with so many clients in different industries, from finance to media to charity to met and livestock handling(!), and many others. I got to play different roles, as a team member, as a mentor, as an appointed tech lead, etc. etc.

I spoke at ALT .NET and SydJS usergroups, and DDD Sydney and NDC Sydney conferences.

And I learned a lot more, not just at work.

I attended several workshops, like Distributed Software Architecture and Messaging by Greg Young, and International Domain Driven Design Tour workshop by Vaughn Vernon. I attended workshops on being a Scrum Product Owner, and a Scrum Master (and learned that I cannot be both at the time time, before they are different interests).

And then I somehow became known as a JavaScript guy. That’s me, who in 2007 was very happy that UpdatePanel in Webforms could abstract AJAX, and thought that people who could write code with Scriptaculous (before jQuery) were magicians. I even run a usergroup for one of popular JavaScript frameworks in Sydney!!!

I’m very proud of what I have done so far, and am still hungry for more learning and achievements. This is a journey that will hopefully never end before I die. One that I wanted to document the past pieces of before I start losing some of the important details.

Thanks for sticking with me till the end. in this long post :)

Cheers,

My Experience Running Jest In A Real Angular CLI Project On Windows

Jest is an alternative test runner by Facebook. It’s popular in React world. I was wondering how it’ll be like in an Angular CLI app. I knew a new semi-empty app won’t be a good enough test, so, I checked it in a real private project I’m working on, so see if it has any benefits for the project and the team.

While I cannot share the project codes, I can still share how the experiment went, as I talked about it yesterday at the ng-sydney Angular usergroup April gathering, also, as I logged it in a related Angular CLI github issue by a friend ng-sydney attendee.

Introduction

For the record, I tried Jest with instructions from @thymikee‘s post (which uses jest-preset-angular), and it worked just fine.

Let me share my findings:

Result Summary

  • It works!
  • It took some small changes.
  • It seemed slower in my case.
  • It caught more errors than Webpack/Karma/Jasmine, which I was very grateful for.
  • The experience is quite different though that I won’t make it the default my team yet.

Required Changes

  • I started with all the steps in the post above.

  • For some libraries I had to follow this github guidance, which made the jest property in my package.json look like:

    The transformIgnorePatterns is the most interesting here. I used \\ because I’m on Windows, and I used ^<rootDir>\\ to try to speed things, although it didn’t make a big difference. There’s also "allowJs": true in "compilerOptions" in src/tsconfig.spec.json.

  • I also had to change the tests. The post mentions all the changes needed (mostly only changing jasmine. to expect. in a few places).

    But there’s another change I had to do, which is removing any expectationFailOutput, which is when you give a custom error message for when a matcher fails to match. Seems Jest does not support that.

Gains

  • Jest seems to run tests in more isolation than the current Webpack/Karma/Jasmine combo, which showed me some errors in my tests that somehow didn’t show before.

  • Jest is reporting which tests are taking too long, the slowness warnings were useful smells to identify not-greatly-written tests.

  • The watch mode is very nice, even though it seems to work off git changes not file watching, which can be confusing at first when you see slightly more files than expected, but it’s still very useful.

Issues

  • As mentioned in Required Changes above, now I cannot use custom error messages in my test matchers (that said, it’s true that default matcher errors are very beautiful and obvious, but still).

  • At the moment, it seems the jest-preset-angular initialization code needs optimization. It doesn’t call polyfills.ts and instead calls its own set, even though it’s optimized for Angular CLI (for example, it uses src/tsconfig.spec.json).

    More importantly, it imports the entire rxjs library, which might hide errors when you forget to import some operators.

  • It’s surprisingly slower than the Webpack/Karma/Jasmine combo!

    For my 196 tests, the Angular CLI v1.0.0 default test runner (with Angular 4) takes ~ 55 seconds, while Jest takes ~100 seconds.

    Update: That conclusion might not be accurate. The time I quoted for each test runner is what the test runner reported. This might for example not include Webpack compilation and browser opening in Karma and might be total time in Jest. If so, then the total time would be the same.
    Thanks @hugoccampos for bringing this to my attention.

  • No browser tests debugging apparently (It might be my ignorance here). A bit harder maybe for those starting testing.

    The Angular CLI does a great job at making testing easy for those not used to it.

  • When Jest itself fails to run, for example if I put a badly formatted regex for transformIgnorePatterns shown above, or mess up something in my src/tsconfig.spec.json file, the errors it shows are very cryptic and tell you nothing that can lead to the real issue (unlike matching errors in tests, which are very nice).

Shared Modules In Angular Apps: Providers Best Practices And What Does `forRoot()` Do?

When you write a shared module for your application, that contains stuff you use in other modules in the same app, you typically want all the services in that module shared only once.

If you just put the services in the providers property of the shared module’s NgModule() decorators, you might get weird cases, especially with lazy loading, when it feels like there’s more than one instance of the service. This is bad if you want to have shared state.

So, there is a better way to write your shared modules:

import {NgModule, ModuleWithProviders} from '@angular/core';
import {CommonModule} from '@angular/common';
// ... other imports

export const providers = [
    // ... your shared services here
];

@NgModule({
    declarations: [...],
    imports: [
        CommonModule, 
        SomeLibraryModule,
        ...],
    exports: [
        SomeLibraryModule.forRoot()
        ...
    ]
})
export class SharedModule {
    static forRoot() : ModuleWithProviders {
        return {
            ngModule: SharedModule,
            providers: [...providers]
        };
    }
}

The forRoot() pattern / convention is very common in libraries, and you’ll see it in things like ng-bootstrap and others. The name isn’t special for the compiler / framework, but it’s a common pattern.

When using these, in the imports section you can just import the module itself (which gives you any declarations needed like directives etc), and in the exports use the forRoot() version of the module so that it’s available for consumers of your shared module.

Then in your other application modules you can add SharedModule to their NgModule‘s imports normally, except for the AppModule.

The AppModule will be the only place where you add SharedModule.forRoot(), like:

Then in your AppModule, import it as:

@NgModule({
    declarations: [...],
    imports: [BrowserModule, SharedModule.forRoot(), ...],
    ...
})
export class AppModule {

}

There is one exception to this though. Your tests.

If you are writing any unit tests where you are importing the SharedModule, you will probably need to import the module with its providers, because there is no AppModule in the test.

Something like:

TestBed.configureTestingModule({
    imports: [ SharedModule.forRoot(), ... ]
})
...

If you haven’t already, have a look at the NgModule official documentation. There’s a main guide, and an FAQ page.

And of course let me know if you have any questions / problems.

Successfully Upgrade Your angular-cli App (Beta 28 & Below) To The Latest @angular/cli Beta


Have you had issues moving from angular-cli beta 28.3 or earlier to the newer versions of the CLI?

Try these steps then!

Global Dependency

This one is as easy as:

npm rm -g angular-cli @angular/cli
npm cache clear
npm i -g @angular/cli

Specific Project

Note: You do NOT need to have the CLI installed globally for this (although it’s a good idea).

First, ensure you have an npm script in your package.json file that looks like:

"scripts": {
    "ng": "ng"
    // ....
}

In this tutorial I’ll replace ng calls with npm run ng -- (note final space ) for those people who may not be able to upgrade their global package.

Now, let’s get to real work.

Commit everything you have in git, then:

npm rm angular-cli @angular/cli
npm cache clear
rm -rf node_modules
npm i -D @angular/cli
npm run ng -- update

Accept all files from update (which used to be called init) other than app module and component, especially (but not limited to) angular-cli.json, polyfills.ts, package.json and main.ts.

Then go to git undo any unwanted change (deleted packages from package.json, missing scripts or files from angular-cli.json, etc). Undo entire app module and component files if you accepted them by accident.

Then

npm install
npm run ng -- build

And you can go roll with npm start / npm run ng -- serve.

All good!

Use Yarn Package Manager In Your Angular CLI Projects

Yarn is an awesome tool to reduce the time it takes to install large NPM packages like the Angular CLI. And the Angular CLI is the best tool to kickstart and manage your Angular 2+ projects.

You can use them together, and it’s very easy.

Initial Setup

First, you install Yarn. If you have it installed already, ensure that you have version 0.19.x at least to avoid issues with global packages. You check your Yarn version by running:

yarn --version

Then you need to ensure that the folder where Yarn writes the global packages executable files.

On Windows, the MSI installer should do it for you. For Mac, check the “Path Setup” part in the installation page.

Once done, ensure to open a new terminal after the installation, and test it.

To find what folder to look for:

yarn global bin

Then run echo $PATH (Mac) or echo %PATH% (Windows command prompt) to get the PATH variable and check it.

Installing Angular CLI

OK, so yarn is installed, and it’s installed correctly. Let’s get Angular CLI:

yarn global add @angular/cli

That’s it!

Adding To A New Angular CLI Project

Starting from beta 31, Angular CLI added native Yarn support.

When you create a new project, the CLI goes and runs npm install for you by default. You can tell it not to by passing a --skip-install flag (-si for short) like:

ng new test-project --skip-install

Note: The option was called --skip-npm / -sn before beta 31.

But then you’ll have to go run Yarn yourself

cd test-project
yarn

Running yarn by itself is similar to npm install. It’ll read your package.json file and add the packages to node_modules as needed.

Or… you can just tell the CLI to use Yarn instead of NPM!

ng set --global packageManager=yarn

This way, you don’t need to do anything special when creating new projects. Just go with ng new test-project with no special flags, and the CLI will use Yarn to install the project packages, unless you specify --skip-install.

This is a user-level setting. It does not affect the generated project in any destructive way. It still has a package.json file (because Yarn works just fine with that), and anyone who doesn’t have Yarn can just run npm install.

More on that below. Before that, let’s ensure that everything worked correctly by running npm start or ng serve etc.

All good? Awesome!

Bonus: A Note About Git

When the Angular CLI creates the project, it initializes it as a git repository and git adds all the files it generated. After the Yarn install, you’ll find another file yarn.lock that’s not yet added.

Yarn team recommends adding the file to git, so, you can do just that, and then commit the result as the new project.

git add yarn.lock
git commit -m "initialize new project with Yarn and Angular CLI"

When you push your repository to a remote server, and someone else pulls it, they can run yarn, or simply npm install (because the package.json file is still there and updated), and get going with the project.

Conclusion

Hopefully that was as simple as you expected it to be. If you have any questions, you can just drop me a comment here, or use any alternative way mentioned in the video.

Don’t forget to sign up for my newsletter so that I can share with you all the resources I use to learn this stuff and more.

Cheers,