https://github.com/fabiangosebrink/angulardays-kickstart
The lab repository for the "Angular Kickstart: von 0 auf 100" Workshop at the AngularDays
https://github.com/fabiangosebrink/angulardays-kickstart
Last synced: 2 months ago
JSON representation
The lab repository for the "Angular Kickstart: von 0 auf 100" Workshop at the AngularDays
- Host: GitHub
- URL: https://github.com/fabiangosebrink/angulardays-kickstart
- Owner: FabianGosebrink
- Created: 2018-10-06T10:07:31.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2018-10-10T06:24:45.000Z (over 6 years ago)
- Last Synced: 2025-01-28T18:32:34.277Z (4 months ago)
- Size: 17.7 MB
- Stars: 3
- Watchers: 5
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: readme.md
Awesome Lists containing this project
README
Labs for the angular workshop at the angular days 2018 from [Christian Liebel](https://twitter.com/christianliebel) and [Fabian Gosebrink](https://twitter.com/FabianGosebrink)
## Start
[Stackblitz](https://stackblitz.com/)
## Labs
### 1. Bindings
Start: https://stackblitz.com/fork/angular
Show Labs
#### Interpolation
In your freshly created project, open the file `src/app/app.component.html` and try the following bindings (one after another). You can completely remove the existing contents of this file.1. `{{ 'hallo' }}`
2. `{{ 3 }}`
3. `{{ 17 + 4 }}`
4. `{{ 'Does this work?' }}`
5. `{{ alert('boom') }}`Which values do you see in the preview pane? Are there any error messages?
#### Interpolation II
Now, open the file `src/app/app.component.ts` and introduce a new field called `value` within the `AppComponent` class:```ts
export class AppComponent {
// …
public value = "Hello";
}
```Bind the value of this field to the template file, by adding the following interpolation to `src/app/app.component.html`.
```html
{{ value }}
```Then, `Hello` should show up in the preview pane.
#### Property Binding
1. Declare a new field called `color` on your component instance and initialize it with a CSS color value (e.g., `hotpink`)
`)
2. Create a new `div` element in the AppComponent’s HTML template (Hint: `
3. Bind the value of the field to the background color of the `div` element (Hint—add the following attribute assignment to the `div` node: `[style.backgroundColor]="color"`)The square brackets are not a typo! They might look odd, but it woll work.
#### Event Binding
1. Implement a new method `onClick` on the component instance that opens an alert box (Hint: `public onClick() { alert('Hello!'); }`)
2. Create a new `button` element in the AppComponent’s HTML template (Hint: `Click me.`)
3. Bind the click event of the button to the `onClick` method (Hint—add the following attribute assignment to the `button` node: `(click)="onClick()"`)
4. Implement a new method `onMouseMove` on the component instance that logs to the console (Hint: `console.log('Hello!')`)
5. Bind the `mousemove` event of the button to `onMouseMove`Again, the brackets are not a typo. It will work out just fine.
Show Solution
https://stackblitz.com/edit/angular-5zuu2g
```js
export class AppComponent {
public value = "Hello";
public color = "hotpink";public onClick(): void {
alert('Hello!');
}public onMouseMove(): void {
console.log('Hello!');
}
}
``````html
{{ 'hallo' }}
{{ 3 }}
{{ 17 + 4 }}
{{ 'Does this work?' }}
{{ value }}
TestClick me.
```### 2. Bindings (Event with $event)
Start: https://stackblitz.com/edit/angular-5zuu2g
Show Labs
#### Event Binding (Advanced)
Adjust the implementations of `onClick()` and `onMouseMove()` to print the coordinates of the mouse (instead of printing `Hello!`)Hints:
- `(click)="onClick($event)"`
- `public onClick(event: MouseEvent): void {}`MouseEvent documentation: https://developer.mozilla.org/de/docs/Web/API/MouseEvent
Show Solution
https://stackblitz.com/edit/angular-zyc9xx
```js
export class AppComponent {
public value = "Hello";
public color = "hotpink";public onClick(event: MouseEvent): void {
alert(event.clientX);
}public onMouseMove(event: MouseEvent): void {
console.log(event.clientX);
}
}
``````html
Click me.
```### 3. Pipes
Start: https://stackblitz.com/edit/angular-zyc9xx
Show Labs
#### InterpolationAdjust your value binding from lab #1 to be printed as lowercase (Hint: `{{ value | lowercase }}`).
Then, adjust it to be printed as UPPERCASE.
#### Built-in pipes
Add a new numeric field to your AppComponent (e.g., `public number = 3.14159;`). Bind this field to the template using the pipes:
- `percent`
- `currency`
- `number` (showing five decimal places)Please use three interpolations (`{{ number | … }} {{ number | … }} {{ number | … }}`).
#### Create a new pipe
Right-click the `app` folder and select _Angular Generator_, then _Pipe_.

The pipe should be called `yell`. Open the generated file `yell.pipe.ts`.
Implement the yell pipe as follows:
- The yell pipe should suffix the bound value with three exclamation marks (e.g., `value + '!!!'` or `` `${value}!!!` ``).
- The developer can optionally pass an argument to override the suffix (`args` parameter).| Interpolation | Value |
| ----------------------------- | -------- |
| `{{ value \| yell }}` | Hello!!! |
| `{{ value \| yell:'???' }}` | Hello??? |Show Solution
https://stackblitz.com/edit/angular-82f7cm
```js
export class AppComponent {
public value = "Hello";
public number = 3.14159;
}
``````js
@Pipe({
name: 'yell',
})
export class YellPipe implements PipeTransform {
transform(value: string, args: string): any {
const suffix = args || '!!!';return `${value}${suffix}`;
}
}
``````html
{{ value | uppercase }}{{ number | percent }}
{{ number | currency }}
{{ number | number:'0.5' }}{{ value | yell }}
{{ value | yell:'???' }}
```### 4. Components
Start: https://stackblitz.com/edit/angular-82f7cm
Show Labs
#### Create a new componentRight-click the `app` folder and select _Angular Generator_, then _Component_.

The new component should be named `todo`. Which files have been created? What’s the selector of the new component (`selector` property of `todo.component.ts`)?
#### Use the new component in your AppComponent’s template
Open the AppComponent’s template (i.e., HTML file) and use the new component there by adding an HTML element with the new component’s selector name (e.g., if the selector is `my-selector`, add `` to the template).
If you like, you can duplicate this HTML element to see the idea of componentization in action.
Show Solution
https://stackblitz.com/edit/angular-jz9ivj
todo.component.ts
```js
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css'],
})
export class TodoComponent implements OnInit {
constructor() {}ngOnInit() {}
}
```app.component.html
```html
```
### 5. Input/Output
Start: https://stackblitz.com/edit/angular-jz9ivj
Show Labs
#### Input1. Extend your `TodoComponent` with an `@Input` field called `todo`.
2. Add a new `myTodo` field to the AppComponent and assign a todo object to it: `{ name: "Wash clothes", done: false, id: 3 }`
3. Pass the `myTodo` object to the `todo` component from the AppComponent’s template by using an input binding.
4. In the `TodoComponent`’s template, bind the value of the `todo` field to the UI using the `JSON` pipe.#### Output
1. Extend your `TodoComponent` with an `@Output` field called `done`.
2. Add a `button` to your `TodoComponent` and an event binding for the `click` event of this button. When the button is clicked, emit the `done` event. Pass the current todo object as the event argument.
3. In the `AppComponent`’s template, bind to the `done` event using an event binding and log the finalized item to the console.Show Solution
https://stackblitz.com/edit/angular-3bhmzs
todo.component.ts
```js
import { Input, Output, EventEmitter, OnInit } from '@angular/core';@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {@Input() todo: any;
@Output() done = new EventEmitter();
constructor() { }
ngOnInit() {
}markTodoAsDone(){
this.done.emit(this.todo);
}}
```todo.component.html
```html
inside todo-component:
{{todo | json}}mark as done
```app.component.html
```html
```
app.component.ts
```js
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public todoObject = { name: "Wash clothes", done: false, id: 3 }catchDoneEvent(todo: any) {
console.log(todo)
}
}
```### 6. Directives
Start: https://stackblitz.com/edit/angular-3bhmzs
Show Labs
#### Create a color directiveRight-click the `app` folder and select _Angular Generator_, then _Directive_. Create a directive (e.g., named `color`) that takes a color as an input binding. The directive should set the color of the host element (using a host binding).
#### Create a click directive
Create another directive (e.g., named `click`) that adds a click handler to the elements where it’s placed on. Whenever the item is clicked, log a message to the console.
Show Solution
https://stackblitz.com/edit/angular-ar3wnk
todo.component.ts
```js
import { Input, Output, EventEmitter, OnInit } from '@angular/core';@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {@Input() todo: any;
@Output() done = new EventEmitter();
colorToBind = "blue";
constructor() { }
ngOnInit() {
}markTodoAsDone(){
this.done.emit(this.todo);
}
}
```todo.component.html
```html
inside todo-component:
{{todo | json}}
test to apply directive onmark as done
```color.directive.ts
```js
import { Directive, Input, HostBinding } from '@angular/core';@Directive({
selector: '[appColor]'
})
export class ColorDirective {@HostBinding('style.color')
@Input() color: string;
}
```click.directive.ts
```js
import { Directive, Input, HostListener } from '@angular/core';@Directive({
selector: '[appClick]',
})
export class ClickDirective {
@HostListener('click', ['$event'])
handleClick($event): void {
console.log('a message');
}constructor() {}
}
```### 7. Dependency Injection/Services
Start: https://stackblitz.com/edit/angular-ar3wnk
Show Labs
#### Injecting ElementRefIn your AppComponent…
1. `import {ElementRef} from '@angular/core';`
2. Request an instance of `ElementRef` via constructor injection
3. Log the instance to the console
4. Inspect it
5. Is the instance provided by the root injector, a module or a component?#### Injection Tokens
In your AppModule…
1. Define an `APP_NAME` injection token (string)
2. Provide it in the module providers and assign it a certain value
3. Consume it from the `AppModule`’s constructor
4. Print the name to the console#### Create a new service
Right-click the `app` folder and select _Angular Generator_, then _Class_.
Create a new model class called `todo` and add the properties:
- `name` (string)
- `done` (boolean)
- `id` (number, optional)Right-click the `app` folder and select _Angular Generator_, then _Service_.
In your TodoService, add the following methods:
- `create(todo: Todo): Todo`
- `get(todoId: number): Todo`
- `getAll(): Todo[]`
- `update(todo: Todo): void`
- `delete(todoId: number): void`Add a very basic, synchronous implementation for getAll. Inject your TodoService into the AppComponent (don’t forget to update the imports on top). Log the list of todos to the console.
Show Solution
https://stackblitz.com/edit/angular-vjgnec
app.component.ts
```js
import { ElementRef } from '@angular/core';@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
public todoObject = { name: "Wash clothes", done: false, id: 3 }constructor(private readonly elementRef: ElementRef,
private readonly todoService: TodoService){
console.log("elementRef from constructor", elementRef);console.log(todoService.getAll());
}catchDoneEvent(todo: any) {
console.log(todo);
}logElementRef(){
console.log("elementRef from console as property", this.elementRef);
}
}
```app.module.ts
```js
import { NgModule, InjectionToken, Inject } from '@angular/core';
// other importsexport const APP_NAME = new InjectionToken('app-name');
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent,
HelloComponent,
YellPipe,
TodoComponent,
ColorDirective,
ClickDirective ],
providers: [{ provide: APP_NAME, useValue: 'My cool app' }, TodoService],
bootstrap: [ AppComponent ]
})
export class AppModule {
constructor(@Inject(APP_NAME) appName: string){
console.log(appName);
}
}```
todo.ts
```js
export class Todo {
name: string;
done: boolean;
id?:number;
}
```todo.service.ts
```js
@Injectable()
export class TodoService {private todos: Todo[] = [];
constructor() { }
create(todo: Todo) { }
get(todoId: number) { }
getAll(): Todo[] {
return this.todos;
}update(todo: Todo): void { }
delete(todoId: number): void { }
}
```### 8. Structural Directives
Start: https://stackblitz.com/edit/angular-vjgnec
Show Labs
#### *ngIfIn your AppComponent’s template, add the following snippet:
```html
Toggle
I’m visible!
```On the component class, introduce a new `show` field and toggle it via a new `toggle()` method (Hint: `this.show = !this.show;`).
#### *ngFor
In the AppComponent, introduce a new field todos and assign the return value of todoService.getAll() to it.
Bind this field to the view using the `*ngFor` structural directive and an unordered list (`ul`) with one list item (`li`) for each todo:```html
```
Next, iterate over your TodoComponent (app-todo) instead and pass the todo via the todo property binding. Adjust the template of TodoComponent to include:
- a checkbox (input) to show the “done” state
- a label to show the “name” text
```html
{{ todo.name }}
```
Show Solution
https://stackblitz.com/edit/angular-mznjjg
app.component.ts
```js
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
private show = true;
todos = [];
constructor(private readonly elementRef: ElementRef,
private readonly todoService: TodoService){
console.log("elementRef from constructor", elementRef);
this.todos = todoService.getAll();
}
catchDoneEvent(todo: any) {
console.log(todo)
}
logElementRef(){
console.log("elementRef from console as property", this.elementRef);
}
toggle() {
this.show = !this.show;
}
}
```
app.component.html
```html
Toggle
I am visible!
- {{todo.name}}
```
todo.service.ts
```js
@Injectable()
export class TodoService {
private todos: Todo[] = [];
constructor() {
this.todos.push({ name: "Wash clothes", done: false, id: 3 });
}
create(todo: Todo) {
}
get(todoId: number) {}
getAll(): Todo[] {
return this.todos;
}
update(todo: Todo): void {}
delete(todoId: number): void {}
}
```
todo.component.ts
```js
import { Import, Output } from '@angular/core';
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
@Input() todo: any;
@Output() done = new EventEmitter();
colorToBind = "blue";
constructor() { }
ngOnInit() {
}
markTodoAsDone(todo: Todo) {
todo.done = !todo.done;
this.done.emit(todo);
}
}
```
todo.component.html
```html
{{ todo.name }}
```
### 9. Observables
Start: https://stackblitz.com/edit/angular-mznjjg
Show Labs
#### Adjust service
Adjust your `TodoService` to now return Observables and upgrade the synchronous value in `getAll()` to an Observable (via `of()`).
- `create(todo: Todo): Observable`
- `get(todoId: number): Observable`
- `getAll(): Observable`
- `update(todo: Todo): Observable`
- `delete(todoId: number): Observable`
#### Use HttpClient
In your AppModule, add HttpClientModule to the imports array
Add a constructor to TodoService and request an instance of HttpClient and use HTTP requests instead of returning synchronous data using the following URLs:
| Method | Action | URL |
| ------ | ---------- | ------------------------------------------ |
| GET | get all | https://tt-todos.azurewebsites.net/todos |
| GET | get single | https://tt-todos.azurewebsites.net/todos/1 |
| POST | create | https://tt-todos.azurewebsites.net/todos |
| PUT | update | https://tt-todos.azurewebsites.net/todos/1 |
| DELETE | delete | https://tt-todos.azurewebsites.net/todos/1 |
Show Solution
https://stackblitz.com/edit/angular-jfyble
app.module.ts
```js
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [BrowserModule, FormsModule, HttpClientModule],
declarations: [
AppComponent,
HelloComponent,
YellPipe,
TodoComponent,
ColorDirective,
ClickDirective,
],
providers: [{ provide: APP_NAME, useValue: 'My cool app' }, TodoService],
bootstrap: [AppComponent],
})
export class AppModule {
constructor(@Inject(APP_NAME) appName: string) {
console.log(appName);
}
}
```
todo.service.ts
```js
@Injectable()
export class TodoService {
private actionUrl = "https://tt-todos.azurewebsites.net/todos"
constructor(private readonly httpClient: HttpClient) { }
create(todo: Todo) {
return this.httpClient.post(this.actionUrl, todo);
}
get(todoId: number) {
return this.httpClient.get(`${this.actionUrl}/${todoId}`);
}
getAll(): Observable {
return this.httpClient.get(this.actionUrl);
}
update(todo: Todo) {
return this.httpClient.put(`${this.actionUrl}/${todo.id}`, todo);
}
delete(todoId: number) {
return this.httpClient.delete(`${this.actionUrl}/${todoId}`);
}
}
```
app.component.ts
```js
import { ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
private show = true;
todos = [];
constructor(private readonly elementRef: ElementRef,
private readonly todoService: TodoService){
console.log("elementRef from constructor", elementRef);
todoService.getAll().subscribe(todos => this.todos = todos);
}
catchDoneEvent(todo: any) {
console.log(todo)
}
logElementRef(){
console.log("elementRef from console as property", this.elementRef);
}
toggle() {
this.show = !this.show;
}
}
```
### 10. Async Pipe
Start: https://stackblitz.com/edit/angular-jfyble
Show Labs
#### Use Async Pipe
Use the `async` pipe instead of manually subscribing.
**Instead of:**
```ts
public todos: Todo[];
```
**Use:**
```ts
public todos$: Observable;
```
**Instead of:**
```ts
todoService.getAll().subscribe(todos => this.todos = todos);
```
**Use:**
```ts
this.todos$ = todoService.getAll();
```
**Instead of:**
```ts
```
**Use:**
```ts
```
Show Solution
https://stackblitz.com/edit/angular-w7g8tc
app.component.ts
```js
import { ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
private show = true;
todos$: Observable;
constructor(private readonly elementRef: ElementRef,
private readonly todoService: TodoService){
console.log("elementRef from constructor", elementRef);
}
ngOnInit() {
this.todos$ = this.todoService.getAll();
}
catchDoneEvent(todo: any) {
console.log(todo)
}
logElementRef(){
console.log("elementRef from console as property", this.elementRef);
}
toggle() {
this.show = !this.show;
}
}
```
app.component.html
```html
You have {{ todos.length }} todos!
-
{{ todo.name }}
```
### 11. Routing
Start: https://stackblitz.com/edit/angular-w7g8tc
Show Labs
#### Generate components
Add the following components:
- TodoListComponent
- TodoEditComponent
- TodoCreateComponent
- NotFoundComponent
#### Define routes
Define/assign the following routes:
- todos
- todos/:id
- todos/new
- **
Redirect the default (empty) route to the todo list.
#### Router outlet
Add a `` to your AppComponent:
```html
```
Then try out different routes by typing them into the address bar.
- Which parts of the page change?
- Which parts stay the same?
#### Router links
In your AppComponent, define two links:
- Home (/todos)
- Create (/todos/new)
In TodoListComponent, request all todos and update the template:
```html
```
#### Active router links
In AppComponent, add routerLinkActive:
```html
Home
```
Or, if you prefer:
```html
Home
```
Add a CSS style for a.my-active
#### Activated route
In TodoEditComponent, listen for changes of the ActivatedRoute and retrieve the record with the given ID from the TodoService and bind it to the view as follows:
```
{{ todo$ | async | json }}
```
Show Solution
https://stackblitz.com/edit/angular-dlrdvt
app.module.ts
```js
import { RouterModule, Routes } from '@angular/router';
const appRoutes: Routes = [
{ path: '', redirectTo: 'todos', pathMatch: 'full' },
{ path: 'todos', component: TodoListComponent },
{ path: 'todos/new', component: TodoCreateComponent },
{ path: 'todos/:id', component: TodoEditComponent },
{ path: '**', component: NotFoundComponent },
];
export const APP_NAME = new InjectionToken() < string > 'app-name';
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
RouterModule.forRoot(appRoutes, { useHash: false }),
],
declarations: [
AppComponent,
HelloComponent,
YellPipe,
TodoComponent,
TodoEditComponent,
TodoListComponent,
TodoCreateComponent,
NotFoundComponent,
ColorDirective,
ClickDirective,
],
providers: [{ provide: APP_NAME, useValue: 'My cool app' }, TodoService],
bootstrap: [AppComponent],
})
export class AppModule {
constructor(@Inject(APP_NAME) appName: string) {
console.log(appName);
}
}
```
app.component.ts
```js
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {}
```
app.component.html
```
todo.component.ts
```js
@Component({
selector: 'app-todo',
templateUrl: './todo.component.html',
styleUrls: ['./todo.component.css']
})
export class TodoComponent implements OnInit {
@Input() todo: any;
@Output() done = new EventEmitter();
constructor() { }
ngOnInit() {
}
markTodoAsDone(todo: Todo) {
todo.done = !todo.done;
this.done.emit(todo);
}
}
```
todo.component.html
```html
```
todo-edit.component.ts
```js
@Component({
selector: 'app-todo-edit',
templateUrl: './todo-edit.component.html',
styleUrls: ['./todo-edit.component.css']
})
export class TodoEditComponent implements OnInit {
public todo$: Observable;
constructor(private readonly activatedRoute: ActivatedRoute,
private readonly todoService: TodoService) { }
ngOnInit() {
this.todo$ = this.activatedRoute.params.pipe(
pluck('id'),
switchMap(id => this.todoService.get(+id))
);
}
}
```
todo-edit.component.html
```html
{{ todo$ | async | json }}
```
### 12. Template Forms
Start: https://stackblitz.com/edit/angular-dlrdvt
Show Labs
#### Add a form
In TodoEditComponent, update the template to contain the following form. It should have to fields: A text field for editing the name and a checkbox for setting the done state. Implement onSubmit and send the updated todo to the server.
```html
Submit!
```
#### Validation
Now, add a required and minlength (5 characters) validation to the name field. Update the submit button to be disabled when the form is invalid:
```html
Submit!
```
Show Solution
https://stackblitz.com/edit/angular-4goufd
todo-edit.component.html
```html
Submit!
```
todo-edit.component.ts
```js
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-todo-edit',
templateUrl: './todo-edit.component.html',
styleUrls: ['./todo-edit.component.css']
})
export class TodoEditComponent implements OnInit {
public todo$: Observable;
constructor(private readonly activatedRoute: ActivatedRoute,
private readonly todoService: TodoService) { }
ngOnInit() {
this.todo$ = this.activatedRoute.params.pipe(
pluck('id'),
switchMap(id => this.todoService.get(+id))
);
}
onSubmit(todo: Todo) {
this.todoService.update(todo).subscribe();
}
}
```