Skip to content

Instantly share code, notes, and snippets.

@LOZORD
Last active April 28, 2018 15:57
Show Gist options
  • Save LOZORD/613f50b6789e914c9cbe3960f06ae1cf to your computer and use it in GitHub Desktop.
Save LOZORD/613f50b6789e914c9cbe3960f06ae1cf to your computer and use it in GitHub Desktop.
Angular (2 ≤) talk for CUNY Hackathon 2017

Let's Hack On Angular

Link to this page: https://goo.gl/p5sZJz

Aloha CUNY Hackathon 2017!!! This has been updated for the November 2017 CUNY Hackathon.

tl;dr

  • I'm Leo Rudberg. I ❤️ hackathons!
  • Thanks to Shaun for helping me debug this!
  • I'll show you how to get an Angular (Version ≥ 2) app up and running.
  • For the purposes of this guide, assume we are using Angular 5 (it's a lot like 2).
  • I'm going to write terminal commands in the "UNIX" style, but this should be very similar for Windows
  • PLEASE let me know if you have questions (find me or comment below).
  • I'm going to assume that you have a small amount of programming knowledge (like at least one college-level CS class). Also, some basic HTML and CSS experience, but I'll explain those parts, anyway.
  • I'm also going to assume that someone (like me) has given a brief intro on Angular and why it is great!
  • LET'S MAKE SOMETHING AWESOME!

Topics

Getting Started

Choose your path...

For the purposes of the CUNY Hackathon, we are taking the second path.

### I wanna hack now!

Ok, go to the Plunker seed which can be seen from the QuickStart page

There does not seem to be an offical Angular Plunker seed. Let's use the Angular CLI instead.

¯\_(ツ)_/¯

I don't mind a little delayed gratification...

Follow along with the directions from the Angular QuickStart guide.

Here's a summary:

First, we need to install a few things!

  • Node
    • Node (and npm) is the server-side (i.e. non-browser) tooling we need to get off the ground.
    • Official downloads
    • There are other ways to download (e.g. homebrew); choose your favorite flavor of installation method.
  • Editor
    • I totally recommend VSCode as it has great TypeScript support.
    • Other editors are ok, as well.
  • Angular CLI
    • The Angular CLI (ng) makes our lives easier by giving us a pre-built setup and codegen.
    • Install info here
  • Finally, make sure you have a modern browser and git ready if you want to use it.
  • Generate a new project following the steps here

Initial launch and explanation

So, you should see an Angular app before you. An Angular app can be thought of as a tree of Components, which change and interact via Service, Pipes, Directives, Observables and other fancy tools.

Although this is (fairly) correct description of Angular, it is not very concrete or beginner friendly.

My concrete description of Angular is that is a framework for building scalable front-end web apps by giving power to HTML through custom Components. Think of Components as handmade, superpowered DOM elements.

With vanilla HTML, we are pretty much stuck with things like <div>, <input>, <img>, and a few others. As web apps get more complex, we need more powerful, abstract components that allow for larger-scale interoperability -- this is where Angular really shines.

Let's start hacking!

You should see "Hello Angular!" in the Plunker demo. To the left are the files we'll be working with. We really only care about the files in the app/ directory.

The Plunker app is an app made of one Component: AppComponent. A Component is just a TypeScript class with a @Component decorator on it.

Inside the decorator, we put:

  • selector: the kabob-case/HTML-like name of the Component
  • template: the HTML template for the component
  • templateUrl: the path to the HTML template (recommended over template)
  • styleUrls: a list of .css files to apply to the Component

Inside the class, we put all of the scope logic for the Component ($scope for Angular 1 users).

In the example, we see that the Component has a field called name which is set to 'Angular'. Try mutating that string and see what happens.

Voila! The magic of Angular!

Next, we will begin working on our own version of hackertyper.com.

Milestone 1: Edit the CSS

You will need to add a CSS file (e.g. app/app.component.css) and put it in the component's styleUrls list if it's not there already.

Use height: 100vh; to make the app take the full screen. Make the background-color: black, the font-family: monospace, and the color: lime or limegreen for the true hacker experience. Adding white-space: pre-line can also make your content behave like its in a <pre>, without needing the actual element.

We will put all of our code in a simple <div> container with the main-content class in our component, so we'll use the .main-content selector to style.

After making all of those changes, app/app.component.css should look something like:

.main-content {
  /* Even without any text, our div occupies the full height of the screen. */
  min-height: 100vh;
  /* Our main content should always take up the full height and width. */
  height: 100%;
  width: 100%;
  /* Center the content naturally. */
  margin: 0 auto;
  /* Preserve the newlines and whitespace in the code. */
  white-space: pre-line;
  /* Finally, make the content look like an old terminal. */
  background-color: black;
  color: limegreen;
  font-family: monospace;
}

Milestone 2: Edit the template file

Similarly, create an app/app.component.html file and make it the value of the templateUrl if it's not already.

Right now, we just need a container element and the content on the page. Like I mentioned earlier, a simple <div> can contain our main content.

After making the edits, your entire app/app.component.html file should look something like:

<div class='main-content'></div>

Milestone 3: Add "hacker" functionality

First, I recommend creating an offset field of the AppComponent class that keeps track of where we are in the other string field, allContent, which contains the complete content that will be written to the screen.

We can add these like so:

// ...
export class AppComponent {
  offset: number = 0;
  allContent: string = '';
  
  // ...
}

Let's add a HostListener that will handle our keypresses on the component. We can add it like so:

@HostListener('document:keydown', ['$event'])
onKeyPress(event: KeyboardEvent) {
  // Add your logic here.
}

What we want to do is, on every keypress, get a random number of characters (~1-5) sliced off from the content, and add it to the view. In otherworks, increment our offset value by a (random) amount.

How does the content get written to the screen though?

We can add the following:

// ... somewhere in the AppComponent class
get viewContent() {
  return this.allContent.slice(0, this.offset);
}

This is a special syntax in both JavaScript and TypeScript that makes it seem like AppComponent has a field call viewContent, but it is secretly a method that uses our other two fields. Accessors like these are useful for when you have data that can be purely computed from other data, but needs to be present in the view.

Finally, we can make our viewContent appear by adding the following in our HTML. Behold, the magic of Angular:

<div class='main-content'>{{viewContent}}</div>

Try typing and see what happens! Be sure to check the Developer Console for any errors that Angular throws.

Milestone 4: Scrolling to the bottom

To make it look like we're actually hacking, let's scroll to the bottom of the container on every keypress. Accomplish this through setting a footer: HTMLElement variable in the ngAfterViewInit lifecycle hook. Then call scrollIntoView on it in your keypress handler.

Here's what I did to accomplish this:

First, I added the AfterViewInit interface to the class, so that Angular knows that we are using a lifecycle hook.

export class AppComponent implements AfterViewInit {

Then, I added a ViewChild to the class. This allows our AppComponent to affect a child element in its DOM.

// ... at the top of the AppComponent class
@ViewChild('footer') footer: HTMLElement;

The string inside the ViewChild decorator means that we want to attach to a footer element.

Next, I set up the element in ngAfterViewInit, the corresponding lifecycle method:

ngAfterViewInit() {
  this.footer = document.getElementById('footer');
}

In our handler for keypresses, just add the following to update the positioning in the view:

this.footer.scrollIntoView(false);

Finally, add the footer to the view:

<div ...>...</div>
<footer id='footer'></footer>

You may need to add a lot of content (I recommend setting allContent to something like:

'Hello Angular!!!!!\n'.repeat(500);.

Milestone 5: Fetching code

Let's create an AppService to fetch code from a (bonus points for randomized) repo from GitHub. You'll need to create a service that uses Http, add it to the AppModule's providers list, and add any other necessary modules.

My AppService looks like this:

import 'rxjs/add/operator/map';

import {Injectable} from '@angular/core';
import {Http} from '@angular/http';

@Injectable()
export class AppService {
  constructor(private http: Http) {}

  getCode(link: string) {
    return this.http.get(link).map(resp => resp.text());
  }
}

We then add the service to the AppModule (app.module.ts):

import {NgModule} from '@angular/core';
import {HttpModule} from '@angular/http';
import {BrowserModule} from '@angular/platform-browser';

import {AppComponent} from './app.component';
import {AppService} from './app_service';


@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, HttpModule],
  providers: [AppService],
  bootstrap: [AppComponent]
})
export class AppModule {}

Then, add it to the constructor to properly inject it.

Use another lifecycle hook, ngOnInit, to fetch the content and assign it to your allContent.

Here's how you inject the service into your component:

constructor(private appService: AppService) {}

Finally, you should have something like this:

ngOnInit() {
  this.appService.getCode(myURL).subscribe(content => this.allContent = content);
}

Now, my component (app.component.ts) looks like:

import {AfterViewInit, Component, HostListener, OnInit, ViewChild} from '@angular/core';

import {AppService} from './app_service';

// I like this file because it has 'batman' in it.
// Feel free to use whatever code you like!
const myURL =
    'https://raw.githubusercontent.com/torvalds/linux/master/net/batman-adv/distributed-arp-table.c';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  offset: number = 0;
  allContent: string = 'Hello world, and welcome to Angular!!!!!\n'.repeat(500);

  @ViewChild('footer') footer: HTMLElement;

  constructor(private appService: AppService) {}

  ngOnInit() {
    this.appService.getCode(myURL).subscribe(
        content => this.allContent = content);
  }

  ngAfterViewInit() {
    this.footer = document.getElementById('footer');
  }

  @HostListener('document:keydown', ['$event'])
  onKeyPress(event: KeyboardEvent) {
    console.log(`got keypress: ${event.key}`);
    const val = this.randomValue();
    if (this.offset + val > this.allContent.length) {
      return;
    }

    this.offset += val;
    this.footer.scrollIntoView(false);
  }

  private randomValue(max = 5) {
    return Math.floor(Math.random() * max);
  }

  get viewContent() {
    return this.allContent.slice(0, this.offset);
  }
}

Conclusion: So how does this work???

Let's talk about how the app bootstraps itself.

  • index.html has <my-app>, which is the selector of the AppComponent.
  • main.ts bootstraps the AppModule as the root of the tree.
  • AppComponent's selector becomes visible to the App, and then it is drawn to the page.
@LOZORD
Copy link
Author

LOZORD commented Apr 29, 2017

(Rough) solution here: https://goo.gl/XqbgIR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment