Tasks UI (Angular Routing)




As we have started with backend for Tasks let's focus on Frontend side too. We need to create lists/create/edit/delete component for Tasks in our Angular app.

Create template for tasks angular component
cd ./frontent && ng g c tasks

Before we start to work on component itself let's add global navigation to the Angular app and bind our task component to the /tasks endpoint.

./frontent/src/app/app.component.html
<div>
    <div class="slds-tabs_default">
        <ul class="slds-tabs_default__nav" style="background-color: #F3F3F4;">
            <li class="slds-tabs_default__item" [ngClass]="{'slds-is-active': url=='tasks'}">
                <a class="slds-tabs_default__link" routerLink="/tasks">Tasks</a>
            </li>
        </ul>
        <div class="slds-tabs_default__content">
            <router-outlet></router-outlet>
        </div>
    </div>
</div>

./frontent/src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { RemoteService } from './services/remote.service';
import { Router, RoutesRecognized } from '@angular/router';

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {

    url: string;

    constructor(
        private remoteService: RemoteService,
        private router: Router
    ) {
        this.router.events.subscribe((ev) => {
            if (ev instanceof RoutesRecognized) {
                try {
                    this.url = ev.state.root.firstChild.url[0].path;
                } catch(err) {}
            }
        });
    }

    ngOnInit() {

    }

}

Nothing special here except hack with Router to get current path and use it to highlight selected menu item.

And the last part

./frontent/src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TaskComponent } from './task/task.component';


const routes: Routes = [
    { path: 'tasks', component: TaskComponent },
    { path: '**', redirectTo: '/tasks' }
];

@NgModule({
    imports: [RouterModule.forRoot(routes, { useHash: true })],
    exports: [RouterModule]
})
export class AppRoutingModule { }

If all works fine you should see new main menu under the header

Now is time for Task list table with main action buttons.

./frontent/src/app/task/task.controller.html
<div>
    <div style="padding: 0 15px 15px 15px;">
        <button (click)="getTasks(true)" class="slds-button slds-button_neutral">Refresh</button>
        <button (click)="showNewTaskModal()" class="slds-button slds-button_brand">New Task</button>
    </div>
    <table class="slds-table slds-table_bordered slds-table_cell-buffer">
        <thead>
            <tr class="slds-text-title_caps">
                <th scope="col">
                    <div class="slds-truncate" title="Id">Id</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Name">Name</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Trigger">Trigger</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Status">Status</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Last Run">Last Run</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Last Run Status">Last Run Status</div>
                </th>
                <th scope="col">
                    <div class="slds-truncate" title="Next Run">Next Run</div>
                </th>
                <th scope="col" style="width: 200px;">
                    <div class="slds-truncate" title="Actions" class="c-right">Actions</div>
                </th>
            </tr>
        </thead>
        <tbody>
            <tr *ngFor="let task of tasks">
                <td data-label="Id">
                    {{task.id}}
                </td>
                <td data-label="Name">
                    <a href="javascript:void(0);" (click)="selectTask(task)">{{task.name}}</a>
                </td>
                <td data-label="Trigger">
                    {{task.config?.trigger_value}}
                </td>
                <td data-label="Status">
                    {{task.status}}
                </td>
                <td data-label="Last Run">
                    {{task.last_run_at | date:'MMM d, y HH:mm':'+0000'}}
                </td>
                <td data-label="Last Run Status">
                    {{task.last_run_status}}
                </td>
                <td data-label="Next Run">
                    {{task.next_run_at | date:'MMM d, y HH:mm':'+0000'}}
                </td>
                <td class="c-right">
                    <button (click)="startTask(task)" class="slds-button slds-button_neutral">Start</button>
                    <!-- <button (click)="stopTask(task)" class="slds-button slds-button_neutral">Stop</button> -->
                    <button (click)="runTask(task)" class="slds-button slds-button_neutral">Run</button>
                    <button (click)="editTask(task)" class="slds-button slds-button_neutral">Edit</button>
                </td>
            </tr>
        </tbody>
    </table>
</div>

./frontent/src/app/task/task.controller.ts
import { Component, OnInit } from '@angular/core';
import { ITask } from '../interfaces';
import { RemoteService } from '../services/remote.service';

@Component({
    selector: 'app-task',
    templateUrl: './task.component.html',
    styleUrls: ['./task.component.scss']
})
export class TaskComponent implements OnInit {

    tasks: ITask[];

    constructor(private remoteService: RemoteService) { }

    getTasks() {
        this.remoteService.getTasks()
        .then((data) => {
            console.log(data);
            this.tasks = data;
        });
    }

    showNewTaskModal() {
        alert('TODO ...');
    }

    startTask(task: ITask) {
        alert('TODO ...');
    }

    stopTask(task: ITask) {
        alert('TODO ...');
    }

    runTask(task: ITask) {
        alert('TODO ...');
    }

    editTask(task: ITask) {
        alert('TODO ...');
    }


    ngOnInit() {
        this.getTasks();

    }

}

and final portion of code is the backend method to get list of tasks:

./backend/controllers/task.py
@task_bp.route('/', methods=['GET'])
@login_required
def get_tasks():

    tasks = Task.query.filter(Task.user_id == current_user.id).all()

    return jsonify(tasks)

...

Now your page should looks like this
Scheduler Jobs list view in Angular App


Let's create separate component for Task Details page

ng g c task-details

and update app-routing.module.ts to use this component for urls like /tasks/<task_id>
...
const routes: Routes = [
    { path: 'tasks', component: TaskComponent },
    { path: 'tasks/:id', component: TaskDetailsComponent },
    { path: '**', redirectTo: '/tasks' }
];
...

Add new endpoint to the backend/controllers/task.py
...
@task_bp.route('/<int:task_id>', methods=['GET'])
@login_required
def get_task(task_id):

    task = (Task.query.filter(Task.id == task_id,
                              Task.user_id == current_user.id)
                      .first_or_404())

    return jsonify(task)
...


And task-details.component.ts Few interesting moments here are: url parameter catch, 404 code handling and manual redirect with angular routing.

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { RemoteService } from '../services/remote.service';
import { ITask } from '../interfaces';

@Component({
    selector: 'app-task-details',
    templateUrl: './task-details.component.html',
    styleUrls: ['./task-details.component.scss']
})
export class TaskDetailsComponent implements OnInit {

    task: ITask = null;

    constructor(
        private route: ActivatedRoute,
        private router: Router,
        private remoteService: RemoteService
    ) {}

    getTask(id: string) {
        this.remoteService.getTask(id)
        .then((data) => {
            console.log(data);
            this.task = data;
        })
        .catch((err) => {
            if (+err.status === 404) {
                this.router.navigate(['/tasks']);
                return;
            }
            throw err;
        });
    }

    ngOnInit() {
        const taskId = this.route.snapshot.paramMap.get('id');
        console.log('TASK ID:', taskId);
        this.getTask(taskId);
    }

}

Now Task View page should looks this way. At this moment it just show task as JSON. This page should be very complex in the end so we will work on different section for this page during few next acticles.



Comments

Popular posts from this blog

HTTPS in local environment for Angular + Flask project.

Salesforce Authentication 2 (Token Validation and Refresh)

User authentification with Flask-Login