-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Gregor Woiwode edited this page Jan 19, 2024
·
6 revisions
- Open your terminal
- Execute
npm start -- --open
starting your Angular application - Visit http://localhost:4200
- Have a look at the beautiful start page the Angular team has prepared for you ❤️ .
- Open your favourite editor and open
src/app/app.component.html
of your application - Find the
span
element having the content Welcome - Change the content from Welcome to whatever you like (e.g. Hello Porsche 2019! 😉)
🕵️♂️ Plesae note that the application automatically recompiles and refreshes automatically.
- Add a second property to
app.component.ts
- You can name your component what ever you like (e.g. name, age, street, location, hobby, ...)
- Bind your property in the template
app.component.html
using double curly braces
- Open your terminal
- Execute
npm run ng generate component todo/todo-checker
- ☝️ Recognize that the
app.module.ts
was updated too and that theTodoCheckerComponent
has been registered. - Open
src/app/todo/todo-checker.component.html
and paste the following template<!-- todo-checker.component.html --> <div class="todo"> <label class="todo__label"> {{ "TODO: Bind the value of the todo's text" }} <input type="checkbox" /> <span class="todo__checkmark"></span> </label> </div>
- Open
app.component.html
- Replace the existing template with your component
<!-- app.component.html --> <ws-todo-checker></ws-todo-checker>
- Finally, have a look at your browser. Your component should be displayed.
We prepared some css-Styles for you.
You can check them out in the directory client/src/scss/
.
- Open
todo-checker.component.ts
- Add a property
todo
, which is an object defining a stringtext
and a boolean flagisDone
. - Bind the
todo
to the HTML template. - Create an event binding for the checkbox listening for the
change
-Event. - Every time the change event is fired the method
emitToggle
should be executed. - Implement the method
emitToggle
that shows analert
-message for the moment.
<!-- Binding with curly braces -->
{{ todo.text }}
<!-- Property binding -->
<input [checked]="...">
<!-- Event binding -->
<input (change)="...">
- Open
todo-checker.component.ts
- Make the property
todo
to be an@Input
- Define an
@Output
calledtoggle
and initialize it with anEventEmitter
- Make
toggle
emit whenever thechange
-Event of the checkbox is fired - Open
app.component.ts
- Make the
AppCompoenent
pass a todo-Object (withtext
andisDone
) to the<ws-todo-checker>
using the property-binding - Handle the
toggle
-Event ofws-todo-checker
by showing analert
-Window displaying the respectivetodo
whenever the event is fired
// Imports
import { Input, Output } from '@angular/core';
// Input-Binding
@Input() todo;
// Output-Binding
@Output() toggle = new EventEmitter();
this.toggle.emit(/* ... */);
<!-- Property binding -->
<ws-todo-checker [todo]="...">
<!-- Event binding -->
<ws-todo-checker (toggle)="notify($event)" >
- open
app.component.ts
. - replace the single
todo
with a collection of at least 2todos
. - bind
todos
to the template using*ngFor
. - adjust the event binding
toggle
calling the methodcheckOrUncheckTodo
(see hints) updating the respective todo.
<ws-todo-checker
[todo]="todo"
(toggle)="checkOrUncheckTodo($event)"
*ngFor="let todo of todos"
><ws-todo-checker>
// NgFo needs to be imported by AppComponent
imports: [NgFor, TodoCheckerComponent],
// ...
todos = [/* ... */]
// ...
checkOrUncheckTodo(todoForUpdate) {
this.todos = this.todos.map(todo => todo.text === todoForUpdate.text
? { ...todo, isDone: !todo.isDone }
: todo
);
}
- Generate an interface executing
ng generate interface
models/todo(It creates a directory model and places a file named
todo.ts` in it)- define 2 properties:
text: string; isDone: boolean;
- define 2 properties:
- Look up every place you are using
todo
(e.g.app.component.ts
&todo-checker.component.ts
) - Annotate each
todo
-property with your created typeTodo
@Input() todo: Todo;
@Output() toggle = new EventEmitter<Todo>();
- Install the extension Angular Language Service to have Auto-Completion in your templates.
- Open your terminal
- Generate a new component by running
ng g c todo-quick-add
- Open
src/app/todo/todo-quick-add.component.html
and paste the following template<!-- todo-quick-add.component.html --> <input <!-- DEFINE THE TEMPLATE REFERENCE --> type="text" class="todo__input" placeholder="What needs to be done?" (keydown.enter)="emitCreate(<!-- PASS THE TEMPLATE REFERENCE TO YOUR COMPONENT -->)" /> <button (click)="emitCreate(<!-- PASS THE TEMPLATE REFERENCE TO YOUR COMPONENT -->)" class="todo__button--primary"> Add </button>
- Implement the method
emitCreate(input: HTMLInputElement)
- Make
emitCreate
emit the eventcreate
(You need to define an @Output for that) - The event
create
should transport thetext
coming from the input-Field. - Furthermore the property
isDone
should initially be set tofalse
. - Open
app.component.html
- Handle the
create
-Event and add the providedTodo
to the existing collecitontodos
- Every time you enter a new todo the list of
<ws-todo-checker>
should automatically grow.
- Generate a new
TodosModule
- Generate a
TodosComponent
that is the root component of Todos feature - Move all existing components used by the todos feature into the newly created
/todos
folder - Move component declarations of those components from
AppModule
intoTodosModule
- Move todo markup from
app.component.html
intotodos.component.html
- Move todos array and methods from
app.component.ts
intotodos.component.ts
- Export
TodosComponent
fromTodosModule
- Import
TodosModule
inAppModule
- Use the new
TodosComponent
inapp.component.html
ng generate module todos
ng generate component todos
@NgModule({
declarations: [
TodosComponent,
// ...
],
exports: [TodosComponent]
})
export class TodosModule {}
<!-- app.component.html -->
<ws-todos></ws-todos>
- Generate a
TodosService
in a/shared
sub-folder of/todos
(this is an Angular convention to show that this service will be used by multiple components). This service will handle all todo data operations. - Move the todo array from
TodosComponent
into your new service. - Create a method
getAllTodos
that returns the array and use it inTodosComponent
.
Bonus
- Create method
create
(that adds a todo) and use it inTodosComponent
. - Create method
update
(that replaces a todo) and use it inTodosComponent
.
ng generate service todos/shared/todos
- open
main.ts
- use
provideHttpClient
import { provideHttpClient } from '@angular/common/http';
// ...
bootstrapApplication(AppComponent, {
providers: [provideHttpClient()],
}).catch(err => console.error(err));
- Switch to the directory
server/
- Run
npm start
- Open
todos.service.ts
- Implement the method
query
that loads all todos from the API (URL: http://localhost:3000/todos) - Bind the loaded todos via subscribe
- Checkout http://localhost:4200 if the todos from the API are displayed
- You can find a detailed documentation of the API in the server's README
- Make sure to subscribe in the
todos.component.ts
- Make sure to call the service in
ngOnInit
// todos.service.ts
constructor(private http: HttpClient) {}
query(): Observable<Todo[]> {
return this.http.get<Todo[]>('http://localhost:3000/todos');
}
// todos.component.ts
ngOnInit(): void {
this.todosService.query().subscribe(todosFromApi => this.todos = todosFromApi);
}
- Enhance
todos.service.ts
to store a todo in the database
💡 Please note, after storing a todo you need to manually refresh the page to see the created todo in the list. We will take care about this UX-fail in the next step.
return this.http.post<Todo>('http://localhost:3000/todos', newTodo);
- When a todo is checked it should immediately persisted (using Put-Request)
- When a todo is deleted it should immediately removed (using DELETE-Request)
- If you want to add a delete button to
<todo-checker>
you can use the following template<div class="todo"> <label class="todo__label" [ngClass]="{ 'todo--is-done': todo.isDone }"> {{ todo.text }} <input type="checkbox" [checked]="todo.isDone" (change)="emitToggle()" /> <span class="todo__checkmark"></span> </label> <!-- ⭐️ NEW NEW NEW ⭐️ --> <span class="todo__delete" (click)="emitRemove()"></span> </div>
- If you want to add a delete button to
- Open
todos.component.ts
. - Use the operator
switchMap
loading all todos after a todo has been created successfully. - Check http://localhost:4200 to see that the list of todos updates automatically.
import { switchMap } from 'rxjs/operators';
createTodo(newTodo: Todo) {
this.todosService
.create(newTodo)
.pipe(switchMap(() => this.todosService.query()))
.subscribe(todos => (this.todos = todos))
}
- Open
todos.component.ts
. - Add a new property
sink
- Initialize
sink
withnew Subscription
- Go through the component code and add each
subscribe
-Block to yoursink
- Implement
OnDestroy
- Call
this.sink.unsubscribe()
inngOnDestroy
cleaning up all Subscriptions.
import { Subscription } from 'rxjs';
sink = new Subscription();
sink.add(/* ... */);
sink.unsubscribe();
- Create an
AppRoutingModule
that- imports
RouterModule
- configure
RouterModule
(usingRouterModule.forRoot()
) to- map route
'todos'
toTodosComponent
- redirect to
'todos'
when url path is empty
- map route
- Re-exports
RouterModule
- imports
- Import
AppRoutingModule
in AppModule - use
router-outlet
to show todos inapp.component.html
💡 If your todos are shown 2 times, try removing the direct view child (
ws-todos
).
const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'todos' },
{ path: 'todos', component: TodosComponent }
];
- Create a
TodosRoutingModule
like you did withAppRoutingModule
and import it inTodosModule
- Configure route
{ path: 'todos/:query', component: TodosComponent }
inTodosRoutingModule
- Change AppRoutingModule routes to
[{ path: '', pathMatch: 'full', redirectTo: 'todos/all' }]
- Create a todos navigation component called
todos-link-navigation
as part ofTodosModule
(see hints for html content):ng generate component todos/todos-link-navigation
- Add the
<ws-todos-link-navigation></ws-todos-link-navigation>
totodos.component.html
- Extend the
query()
method oftodos.service.ts
to accept an optional string parameter for filtering the results:
query(param?: string): Observable<Todo[]> {
return this.http.get<Todo[]>(`${todosUrl}?query=${param ? param : 'all'}`);
}
- Use
ActivatedRoute#paramMap
to read out the dynamicquery
url-parameter:
// todos.component.ts ngOnInit():
this.route.paramMap
.pipe(
switchMap(paramMap => this.todosService.query(paramMap.get('query')))
)
.subscribe(todos => (this.todos = todos)
<!-- todos-link-navigation.component.html -->
<ul class="todo__link-navigation">
<li class="todo__link-navigation__link">
<a routerLink="../all" routerLinkActive="todo__link--active">All</a>
</li>
<li class="todo__link-navigation__link">
<a routerLink="../active" routerLinkActive="todo__link--active">Active</a>
</li>
<li class="todo__link-navigation__link">
<a routerLink="../complete" routerLinkActive="todo__link--active"
>Complete</a
>
</li>
</ul>
Watch your trainers implement lazy loading :-)