Working with State Management and Angular

Introduction

One of the most challenging things in software development is state management.

Motivation

What is a state (or application state)? Theoretically, it is the entire memory of the application, but, typically, it is the data received via API calls, user inputs, presentation UI State, app preferences, etc.

It is the data that can differentiate two instances of the same application.

One example of application state would be a list of customers or products maintained in an application.

Problem (we’re trying to solve)

Think of an application using a list of data (products, customers, …). This list is the state that we are trying to manage.

Some API calls and user inputs could change the state ( i.e. the list ) by adding or removing items/entries. The state change should be reflected in the UI and other dependent components.

We could solve this with a global variable to hold the list and then add/remove customers from/to it and then write the code to update the UI and dependencies. But, there are many pitfalls in that design which are not the focus of this article.

Solutions

Currently there are several state management libraries for Angular apps: NGRX, NGXS or Akita.

RxJS – Reactive Extensions Library for JavaScript

RxJS is a library for reactive programming using Observables, to make it easier to compose asynchronous or callback-based code. This project is a rewrite of Reactive-Extensions/RxJS with better performance, better modularity, better debuggable call stacks, while staying mostly backwards compatible, with some breaking changes that reduce the API surface

Vorschau(öffnet in neuem Tab)

@ngrx/store

Store is RxJS powered state management for Angular applications, inspired by Redux. Store is a controlled state container designed to help write performant, consistent applications on top of Angular.

Key concepts

  • Actions describe unique events that are dispatched from components and services.
  • State changes are handled by pure functions called reducers that take the current state and the latest action to compute a new state.
  • Selectors are pure functions used to select, derive and compose pieces of state.
  • State is accessed with the Store, an observable of state and an observer of actions
NgRx State Management Lifecycle Diagram

Akita

Akita is a state management pattern, built on top of RxJS, which takes the idea of multiple data stores from Flux and the immutable updates from Redux, along with the concept of streaming data, to create the Observable Data Stores model.

Akita encourages simplicity. It saves you the hassle of creating boilerplate code and offers powerful tools with a moderate learning curve, suitable for both experienced and inexperienced developers alike.

Akita is based on object-oriented design principles instead of functional programming, so developers with OOP experience should feel right at home. Its opinionated structure provides your team with a fixed pattern that cannot be deviated from.

NGXS – State management pattern + library for Angular

NGXS is a state management pattern + library for Angular. It acts as a single source of truth for your application’s state, providing simple rules for predictable state mutations.

NGXS is modeled after the CQRS pattern popularly implemented in libraries like Redux and NgRx but reduces boilerplate by using modern TypeScript features such as classes and decorators.

Introduction (taken from here)

What is state?

State is basically everything that will define the UI that our user will be using. State could be whether a button should be visible or not, it could be the result of that button click and it could also be an Array of users that is coming from an API. State can live in different places throughout our entire application. Some state is very specific to a certain component where other state might be shared in different parts of our application. One piece of state could be a singleton instance, where a another piece of state could share the limited lifespan of a component that can be destroyed at any time.

This big variety of what state could be, how long it lives and where it comes from results in complexity that we need to manage.

What is state management?

State management is the concept of adding, updating, removing and reading pieces of state in an application. When we have deeply nested data structures and we want to update a specific part deep down in the tree, it might become complex. In that case we have state management libraries that contain a Store which helps us with state management to get rid of that complexity. A quick note, we have to be careful that these libraries don’t add complexity by overusing them.

Reactive state

Combining state management together with reactive programming can be a really nice way to develop single-page-applications. Whether our focus lies on Angular, Vue or React, combining these two principles will result in more predictable applications.

Now what has state to do with reactive programming? A piece of state can change over time, so in a way we are waiting for new state changes. That makes it asynchronous.

Let’s take this example for instance:

// false------true-----false---true...
sidebarCollapsed$ = this.state.sidebarCollapsed$

The sidebarCollapsed$ stream starts out with false, later on it becomes true and so on. This stream keeps on living. In Angular this state can be consumed with the async pipe as easy as:

<my-awesome-sidebar *ngIf="sidebarCollapsed$|async">
</my-awesome-sidebar>

The async pipe will subscribe to the sidebarCollapsed$ pass it to the component, mark it for check and will automatically unsubscribe when the component gets destroyed. Keeping state in an observer pattern is nice because we can subscribe to the changes. Oh, and did I mention it plays super nice with Angular?

We can either use a BehaviorSubject or state management frameworks that support Observables. Here are some really great ones with Observable support:

Immutability and Unidirectional data flow

Before we dive deeper in state, there are 2 important principles that we should follow when managing state. The first principle is immutability, which means that we should never mutate data directly without creating a new reference of that object. If we mutate data directly, our application becomes unpredictable and it’s really hard to trace bugs. When we work in an immutable fashion we can also take advantage of performance strategies like the ChangeDetection.OnPush from Angular or React its PureComponent.

When we use typescript we can enforce the typescript compiler to complain when we mutate data

type Foo = {
    readonly bar: string; 
    readonly baz: number; 
}

let first = {bar: 'test', baz: 1};
first.bar = 'test2'; // compilation error
first = {...first, bar: 'test2'}; // success

In the previous example we have overwritten the first instance with an entire new instance that has an updated bar property.

Arrays can be handled like this:

let arr = ['Brecht', 'Kwinten'];
arr.push('John'); // BAD: arr is mutated
arr = [...arr, 'John']; // Good, arr gets new reference

the Array prototype also has some great helper functions that we can use to enforce immutability like map() and filter() but this is not in scope for this article.

The second principle is Unidirectional data flow. In a nutshell, this means that we should never use two-way data binding on state. It is the absolute owner of that specific piece of state that is in charge of updating it (immutable of course).

Both of these principles are highly enforced by the Redux pattern.

What kind of states are there?

Router state

Often forgotten, but one of the most important pieces of state a web application can have. Putting state in the route gives us the following advantages:

  • We can use the browser navigation buttons
  • We can bookmark the state
  • We can can copy and paste the url with the state to other users
  • We don’t have to manage it, it’s always there in the route

Tip: Instead of handling modals with a userDetailModalVisible property, why not enjoy all the benefits mentioned above and bind it to a users/:userId route? Using a child router-outlet in Angular makes this a piece of cake as we can see in this snippet.

<table>
<!--contains users -->
</table>
<router-outlet>
<!-- user detail modal rendered in here -->
</router-outlet>

Component state

Every component could contain state. That state could be shared with its dumb components or could be used in the component itself. Eg: When an ItemComponent has a property selectedItems which is an array of ids, and that array is never used in other components (that aren’t children of that component), we can consider it component state. It belongs to that component, therefore the component should be responsible for it. Child components can consume that state but should never mutate it. Those components can notify their parent that is responsible for it, which could update it in an immutable way. For more information about smart and dumb components look here.

Personally, I try to avoid state management frameworks for managing component state because it’s the responsibility of that component to manage that state. There are however good reasons to use state management frameworks to manage component state:

If the state management of the component becomes a bit too complex and we don’t want to use a state management framework just yet, we could use a state reducer in the component itself.

Persisted state

Persisted state, is state that is being remembered when the user navigates between different pages. This could be whether a sidebar was collapsed or not, or when the user returns to a grid with a lot of filters and he wants them to be remembered and reapplied when he returns. Another example is a wizard with different steps, and every step needs to be persisted so the user can navigate back and forth and the last page is a result of all these steps.

Persisted state is the type of state where we typically use a state management framework for, that being said, if we don’t want to rely on an external dependency we can also manage it in a Angular service which can be a singleton that is shared throughout the entire application. If that service becomes too complex or there is a lot of state to manage, I would consider to put that state into a state management framework.

Shared state

When we are talking about shared state, we are talking about state that needs to be shared between different parts of our application. State that is being shared throughout different smart components. This means that the instance of this piece of state should live on a higher level, than the components that want to consume it.

Shared state can be managed in a state management framework like ReduxNgrxAkitaNgxs and so on, but if that state is small and simple we can also manage it manually. Let’s say that we want an Observable of an Array of countries that we need to share throughout the entire application. In Angular we could have a CountryService that fetches the countries from the API once, and then shares it throughout the entire application. For that we can use the shareReplay operator from RxJS.

export class CountryService {
    ...
    countries$ = this.httpClient.get('countries').pipe(shareReplay(1));
}

Simple right, one line of code?! For this we don’t need a state management framework, although it can also have its benefits. Some developers like to keep all their master data in a Redux store, and that’s fine. Just know that we don’t have to. I like to develop by the KISS principle (Keep ISimple Stupid) as much as possible, so I favor this approach many times. Think about the amount of lines of code we saved by this approach. Beware that every line of code we write, not only needs to be written but also maintained.

Which state needs to be managed?

Now that we know what state is, we have to ask ourselves which state needs to be managed, and where do we manage that state? In a component, singleton service or a framework (Store)?

This is the part where the strong opinions surface. I would suggest to use what works for you and your team and really think about, but here are my personal opinionated guidelines:

  • I try to avoid state management frameworks where possible. RxJS already leverages us with a lot already and I like to think KISS.
  • I try to avoid using state management frameworks to communicate with different parts in my application, I believe state is unrelated to communication.
  • When my component can handle the state and it’s not too complex, I let my component in charge of managing that state.
  • Master data like countries are exposed in a service which uses the shareReplay operator.
  • I don’t put the result of a getById API call into a store if there is no one consuming that state except for the component requesting it
  • I use a facade between my smart components and my store/services to make refactoring easier in the future.

However, there is also a popular opinion out there to put literally everything in the store which has the following advantages:

  • We can see the flow of the code in devtools
  • Consistent pattern
  • We can leverage selectors with memoization
  • Easier for realtime applications
  • Optimistic updates are easier

However, there are a few downsides as well:

  • A gigantic amount of bloat code: Bigger bundle size, more maintenance and dev time. Eg: If we would use the complete Ngrx pattern for the countries$ example we would have to write an: actionactiontypeeffect and a reducer.
  • Tightly coupled to a strong dependency that is hard to get rid of in the future
  • Generally more complex
  • The user his screen can get out of sync with the backend
  • Cache invalidation: if we add a currentUserToEdit in the store, we have to get it out when we navigate away
  • We can’t use the async pipe to cancel pending XHR requests
  • We create a distributed monolith of some sort

More to read

Working with Angular

Styling the user interface: Bootstrap

Install Bootstrap

Adding Bootstrap From CDN

Add the following lines into src/index.html

<!doctype html>
<html>
<head>
...
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
</head>
<body>
  <app-root>Loading...</app-root>

  <script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
</body>
</html>

Adding Bootstrap via NPM

Another way to add Bootstrap to your Angular project is to install it into your project folder by using NPM.

$ npm install bootstrap jquery popper.js --save

Add the installed files into src/angular.json

...
"styles": [
    "styles.css",
    "../node_modules/bootstrap/dist/css/bootstrap.min.css"
  ],
  "scripts": [
    "../node_modules/jquery/dist/jquery.min.js",
    "../node_modules/bootstrap/dist/js/bootstrap.min.js"
  ],

Check Bootstrap

Add the following lines into src/app/app.components.html

<div class="container">
  <div class="jumbotron">
    <h1>Welcome</h1>
    <h2>Angular & Bootstrap Demo</h2>
  </div>
  <div class="panel panel-primary">
    <div class="panel-heading">Status</div>
    <div class="panel-body">
      <h3>{{title}}</h3>
    </div>
  </div>
</div>

Adding Angular Bootstrap Extensions

https://github.com/valor-software/ngx-bootstrap

Use the Angular CLI ng add command for updating your Angular project.

ng add ngx-bootstrap

Or use ng add to add needed component (for example tooltip).

ng add ngx-bootstrap --component tooltip

Add component to your page:

<button type="button" class="btn btn-primary"
        tooltip="This is a Bootstrap Button created with ngx-bootstrap.">
  Simple demo
</button>

Styling the user interface: Material Design

Install

$ ng add @angular/material

Install ‘hammer.js’

HammerJS can be installed using the following npm command:

   npm install --save hammerjs

After installing, import it on your app’s entry point (e.g. src/main.ts).

 import 'hammerjs';

Adding

Checking

Add Material Design Components

Installation

Installation of current/latest version

$ npm install -g @angular/cli
$ npm -g install @angular/cli@latest

Installation of next developer version

$ npm -g install @angular/cli@next

Usefull tools and extensions

Formatting source code

Angular + Prettier + Husky

Read this great tutorial.

Working with Azure and Python

First Step: Hello World Sample

The following steps at borrowed from the quick start tutorial.

Download sample repository

$ git clone https://github.com/Azure-Samples/python-docs-hello-world
$ cd python-docs-hello-world

Create virtual environment

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ export FLASK_APP=application.py
$ flask run

Login zu Azure

$ az login

Deploy to App Service

$ az webapp up --sku F1 -n azure-toolbox-flask-demo -l westeurope
webapp azure-toolbox-flask-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Python ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-flask-demo.azurewebsites.net
{
  "URL": "http:/azure-toolbox-flask-demo.azurewebsites.net",
  "appserviceplan": "xx_asp_Linux_westeurope_0",
  "location": "westeurope",
  "name": "azure-toolbox--flask-demo",
  "os": "Linux",
  "resourcegroup": "xx_xx_Linux_westeurope",
  "runtime_version": "python|3.7",
  "runtime_version_detected": "-",
  "sku": "FREE",
  "src_path": ".../Working-with_Python"
}

Create Django App with PostgreSQL

Installation PostgreSQL on Mac OS

$ brew install postgres
==> Installing dependencies for postgresql: krb5
==> Installing postgresql dependency: krb5
...
==> Installing postgresql
...
==> Caveats
==> krb5
krb5 is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in
parallel can cause all kinds of trouble.

If you need to have krb5 first in your PATH run:
  echo 'export PATH="/usr/local/opt/krb5/bin:$PATH"' >> ~/.bash_profile
  echo 'export PATH="/usr/local/opt/krb5/sbin:$PATH"' >> ~/.bash_profile

For compilers to find krb5 you may need to set:
  export LDFLAGS="-L/usr/local/opt/krb5/lib"
  export CPPFLAGS="-I/usr/local/opt/krb5/include"

For pkg-config to find krb5 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/krb5/lib/pkgconfig"

==> postgresql
To migrate existing data from a previous major version of PostgreSQL run:
  brew postgresql-upgrade-database

To have launchd start postgresql now and restart at login:
  brew services start postgresql
Or, if you don't want/need a background service you can just run:
  pg_ctl -D /usr/local/var/postgres start

Set user and passwords for postgres database





Create database and user for django app

$ psql postgres
psql (12.1)
Type "help" for help.

postgres=# CREATE DATABASE pollsdb;
CREATE DATABASE
postgres=# CREATE USER manager WITH PASSWORD '########';
CREATE ROLE
postgres=# GRANT ALL PRIVILEGES ON DATABASE pollsdb TO manager;
GRANT

Download sample repository

$ git clone https://github.com/Azure-Samples/djangoapp.git
$ cd djangoapp

Create virtual environment

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -r requirements.txt
$ cat env.sh
export DBHOST="localhost"
export DBUSER="manager"
export DBNAME="pollsdb"
export DBPASS="supersecretpass"
$ . env.sh
$ python manage.py  makemigrations
No changes detected
$ python manage.py  migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, polls, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying polls.0001_initial... OK
  Applying sessions.0001_initial... OK
 $ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: admin@localhost
Password:
Password (again):
Superuser created successfully.

Run server and acccess web page at http://127.0.0.1:8000/

$ python manage.py runserver
Performing system checks...

System check identified no issues (0 silenced).
January 25, 2020 - 16:42:14
Django version 2.1.2, using settings 'azuresite.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
[25/Jan/2020 16:42:26] "GET / HTTP/1.1" 200 111
[25/Jan/2020 16:42:26] "GET /static/polls/style.css HTTP/1.1" 200 27
Not Found: /favicon.ico
[25/Jan/2020 16:42:26] "GET /favicon.ico HTTP/1.1" 404 2688

Login zu Azure

$ az login

Deploy to App Service

$ az webapp up --sku F1 -n azure-toolbox-django-demo -l westeurope
webapp azure-toolbox-django-demo doesn't exist
Creating Resource group 'xx_xx_Linux_westeurope' ...
Resource group creation complete
Creating AppServicePlan 'xx_asp_Linux_westeurope_0' ...
Creating webapp 'flask-demo' ...
Configuring default logging for the app, if not already enabled
Creating zip with contents of dir .../Working-with_Django ...
Getting scm site credentials for zip deployment
Starting zip deployment. This operation can take a while to complete ...
Deployment endpoint responded with status code 202
You can launch the app at http://via-internet-django-demo.azurewebsites.net
{
  "URL": "http:/azure-toolbox-django-demo.azurewebsites.net",
  "appserviceplan": "xx_asp_Linux_westeurope_0",
  "location": "westeurope",
  "name": "azure-toolbox--django-demo",
  "os": "Linux",
  "resourcegroup": "xx_xx_Linux_westeurope",
  "runtime_version": "python|3.7",
  "runtime_version_detected": "-",
  "sku": "FREE",
  "src_path": ".../Working-with_Django"
}

Additional Reading

Installation

Here is the documentation from Microsoft.

Mac OS

Install with Homebrew

$ brew update && brew install azure-cli
$ az login

Django | Cookbook

First steps

The following steps are based on a summary of the Django Tutorial

Create project

$ django-admin startproject main
$ cd working_with_django
$ python manage.py migrate
$ python manage.py runserver 8080
$ python manage.py startapp app_base

Create view

Create view in app_base/views.py

from django.http import HttpResponse

def index(request):
    return HttpResponse("Hello, world. You're at the polls index.")

Add view to app_base/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),
]

Add urls to project main/urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('admin/', admin.site.urls),
    path('app_base/', include('app_base.urls')),
]

Create admin user

$ python manage.py createsuperuser
Username (leave blank to use 'user'): admin
Email address: admin@localhost
Password: 
Password (again): 
Superuser created successfully.

Create data and database

Create database model in app_base/models.py

from django.db import models

class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

Activating models in main/settings.py

INSTALLED_APPS = [
    'app_base.apps.AppBaseConfig',

    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]
$ python manage.py makemigrations app_base
$ python manage.py sqlmigrate app_base 0001

Make app modifiable in the admin (app_base/admin.py)

from django.contrib import admin
from .models import Question

admin.site.register(Question)

Writing more views

Create views in app_base/views.py

def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

Add new views into app_base/urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('', views.index, name='index'),

    path('<int:question_id>/', views.detail, name='detail'),
    path('<int:question_id>/results/', views.results, name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

Add template in app_base/templates/polls/index.html

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Modify view in app_base/views.py

from django.shortcuts import render
...
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

Raising a 404 error in app_base/views.py

from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404

from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

Create template app_base/templates/polls/detail.html

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

Removing hardcoded URLs in app_base/templates/polls/index.html

<li>
   <a href="{% url 'detail' question.id %}">{{ question.question_text }}</a>
</li>

The way this works is by looking up the URL definition as specified in the app_base/urs.py

...
# the 'name' value as called by the {% url %} template tag
path('<int:question_id>/', views.detail, name='detail'),
...

Namespacing URL names in app_base/urls.py

app_name = 'app_base'

urlpatterns = [
...

Then, modify link in app_base/templates/polls/index.html

from url ‘detail’ to url ‘app_base:detail’

<li>
    <a href="{% url 'app_base:detail' question.id %}">{{ question.question_text }}</a>
</li>

Use generic views: Less code is better

Create class in app_views/views.py

class HomeView(generic.TemplateView):
    template_name = 'index.html'

Create template app_views/templates/index.html

<h1>App Views:</h1>
Welcome

Modify app_views/urls.py

urlpatterns = [
    path('', views.HomeView.as_view(), name='home'),
]

Add another app to main project

Create app

$ python manage.py startapp app_view

Modify main/urls.py

urlpatterns = [
    path('admin/',     admin.site.urls),
    path('app_base/',  include('app_base.urls')),
    path('app_views/', include('app_views.urls')),
]

Add data model in app_views/models.py

from django.db import models

class DataItem(models.Model):
    text = models.CharField(max_length=200)
    data = models.IntegerField(default=0)

    def __str__(self):
        return self.text

Register data in app_views/admin.py

from django.contrib import admin
from .models import DataItem

admin.site.register(DataItem)

Activate models

$ python manage.py makemigrations app_views
$ python manage.py sqlmigrate app_views 0001
$ python manage.py migrate app_views

Navigation / Redirection

Set root page of Django project

When accessing your Django project, the root page will normaly doesn’n show your app homepage.

To change this, you hate to modiy the url handling.

In the following sample, replace <appname> with the name of your app

Define a redirection view in your app (/<appname>/urls.py)

def redirect_to_home(request):
    return redirect('/<appname>')

Define path in the global urls.py (/main/urls.py)

from django.contrib import admin
from django.urls import include, path
from django.shortcuts import redirect

from <appname> import views

urlpatterns = [
    path('',            views.redirect_to_home, name='home'),
    path('<appname>/',  include('<appname>.urls')),
    path('admin/',      admin.site.urls)
]

Highlight current page in navigation menu

<div class="list-group">
    <a href="{% url 'app:upload:basic' %}" class="list-group-item{% if request.path == '/upload/basic/' %} active{% endif %}">
            Basic Upload
    </a>
    <a href="{% url 'app:upload:progress' %}" class="list-group-item{% if request.path == '/upload/progress/' %} active{% endif %}">
            Progress Bar Upload
    </a>
</div>

Additional reading

Tutorials

Testing

Blogs and Posts

Resolving problems

Wrong template is used

The template system is using a search approach to find the specified template file, e.g. ‘home.html’.

If you created more than one apps with the same filenames for templates, the first one will be used.

Change the template folders and add the app name, e.g.

template/
        app_base/
                home.html

Resolving error messages and erors

‘app_name’ is not a registered namespace

One reason for this error is the usage of a namespace in a link.

Back to <a href="{% url 'app_views:home' %}">Home</a>

If you want to use this way of links, you have to define the namespace/appname in your <app>/urls.py file

app_name = 'app_views'
urlpatterns = [
    path('', views.HomeView.as_view(), name='home'),
]

dependencies reference nonexistent parent node

  • Recreate database and migration files
  • Remove all migration files under */migrations/00*.py
  • Remove all pycache folders under */__pycache__ and */*/__pycache__
  • Run migration again
$ python manage.py makemigrations
$ python manage migrate

ValueError: Dependency on app with no migrations: customuser

$ python manage.py makemigrations

Project Structure

Running tasks with Makefile

PREFIX_PKG := app

default:
	grep -E ':\s+#' Makefile

clearcache:	# Clear Cache
	python3 manage.py clearcache

run:		# Run Server
	python3 manage.py runserver 8000

deploy:		# Deploy
	rm -rf dist $(PREFIX_PKG)*
	rm -rf polls.dist
	cd polls && python3 setup.py sdist
	mkdir polls.dist && mv polls/dist/* polls/$(PREFIX_PKG)* polls.dist

install_bootstrap:	# Install Bootstrap Library
	cd .. && yarn add bootstrap
	rm -rf  polls/static/bootstrap
	mkdir   polls/static/bootstrap
	cp -R ../node_modules/bootstrap/dist/* polls/static/bootstrap

install_jquery:		# Install jQuery Library
	cd .. && yarn add jquery
	rm -rf polls/static/jquery
	mkdir  polls/static/jquery
	cp ../node_modules/jquery/dist/* polls/static/jquery

install_bootstrap_from_source:	# Install Bootstrap from Source
	mkdir -p install && \
	wget https://github.com/twbs/bootstrap/releases/download/v4.1.3/bootstrap-4.1.3-dist.zip -O install/bootstrap-4.1.3-dist.zip && \
	unzip install/bootstrap-4.1.3-dist.zip -d polls/static/bootstrap/4.1.3

Working with p5.js

Introduction

p5.js is a JavaScript library for creative coding, with a focus on making coding accessible and inclusive for artists, designers, educators, beginners, and anyone else.

In this Post, you will learn how to create an ionic app using p5.js to create an amazing demo of the Lissajous Curve (de/en).

If you want to learn more about this amazing graphs, take a look at some examples at codepen.io. Some amazing examples are here, here and here.

As always, you will find the final project on Github.

Preparation

You will need to install Node.Js, Ionic and p5.js.

First step is, to install Node.Js. I will refer to the installation pages on Node JS.

After installing Node.js, you will also have the Node Package manager, npm. We will use this tool to install the other requirements.

Install Ionic

$ npm -g install ionic

Create base app structure

$ ionic start working-with-p5js blank --type angular
$ cd working-with-p5js

Add p5.js to the ionic app

$ npm install --save-dev p5

Test your base app

$ ionic serve

Starting with p5.js

Add the base components of p5.js to our app.

Modify home.page.html

<ion-header>
	<ion-toolbar>
		<ion-title>Working with p5.js</ion-title>
	</ion-toolbar>
</ion-header>

<ion-content>
	<div id="canvasContainer" class="canvas-container"></div>
</ion-content>

Modify home.page.ts

import { Component, OnInit, ElementRef } from '@angular/core';

import * as p5 from 'p5';

@Component({
	selector: 'app-home',
	templateUrl: 'home.page.html',
	styleUrls: [ 'home.page.scss' ],
})
export class HomePage implements OnInit {

  curve: any;
  canvasSizeX = 200;
  canvasSizeY = 200;

  constructor(private el: ElementRef) { }

  ngOnInit() {
    const p5obj = new p5(p => {
      p.setup = () => { this.setup(p); };
      p.draw = () => { this.draw(p); };
    }, this.el.nativeElement);
  }

  setup(p) {
    const c = document.querySelector('#canvasContainer');
    p
      .createCanvas(this.canvasSizeX, this.canvasSizeY)
      .parent(c);
  }

  draw(p) {
    p.background(220);
  }
}

And the result ist your frist p5.js graphic:

Next, we will grab an example from p5.js: form-regular-polygon

Modify home.page.ts

We modify the draw function and add a required polygon function.

import { Component, OnInit, ElementRef } from '@angular/core';

import * as p5 from 'p5';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html',
  styleUrls: ['home.page.scss'],
})
export class HomePage implements OnInit {

  curve: any;
  canvasSizeX = 720;
  canvasSizeY = 400;

  private ID = 'HomePage';
  log(func, line = '') {
    console.log(this.ID + '::' + func + '|' + line);
  }

  constructor(
    private el: ElementRef
  ) {
    this.log('constructor');
  }

  ngOnInit() {
    this.log('ngOnInit');

    const p5obj = new p5(p => {
      p.setup = () => {
        this.setup(p);
      };

      p.draw = () => {
        this.draw(p);
      };
    }, this.el.nativeElement);
  }

  setup(p) {
    this.log('setup');

    const c = document.querySelector('#canvasContainer');
    p
      .createCanvas(this.canvasSizeX, this.canvasSizeY)
      .parent(c);
  }

  polygon(p, x, y, radius, npoints, color) {
    const angle = p.TWO_PI / npoints;
    p.beginShape();

    p.fill(color);
    for (let a = 0; a < p.TWO_PI; a += angle) {
      const sx = x + Math.cos(a) * radius;
      const sy = y + Math.sin(a) * radius;
      p.vertex(sx, sy);
    }
    p.endShape(p.CLOSE);
  }

  draw_figure(p, scaleX, scaleY, divisor, radius, npoints, color) {
    p.push();
    p.translate(this.canvasSizeX * scaleX, this.canvasSizeY * scaleY);
    p.rotate(p.frameCount / divisor);
    this.polygon(p, 0, 0, radius, npoints, color);
    p.pop();
  }

  draw(p) {
    p.
      background('white');

    this.draw_figure(p, 0.2, 0.5, 200.0, 82, 3, 'red');
    this.draw_figure(p, 0.5, 0.5, 50.0, 80, 20, 'blue');
    this.draw_figure(p, 0.8, 0.5, -100.0, 70, 7, 'green');
  }
}

And we get a nice litte animation:

Additional Resources and Reading

Websites

D3.js | Cookbook

Basic Tasks

Create Container

const svg = d3
	.select('#playground')
	.append('svg')
	.attr('width', width + margin.left + margin.right)
	.attr('height', height + margin.top + margin.bottom)
	.append('g')
	.attr('transform',
	      'translate(' + margin.left + ',' + margin.top + ')');

Axes: Create

const x = d3Scale.scaleLinear().domain([0, 100]).range([0, width]);
const y = d3Scale.scaleLinear().domain([0, 100]).range([height, 0]);

const xAxis = d3Axis.axisBottom(x).tickFormat(d3.timeFormat('%m/%d'));
const yAxis = d3Axis.axisLeft(y).ticks(10);

Axes

Format

var axisTimeFormat = d3.time.format.multi([
    [".%L",   (d) => d.getMilliseconds()],
    [":%S",   (d) => d.getSeconds()],
    ["%H:%M", (d) => d.getMinutes()],
    ["%H:%M", (d) => d.getHours()],
    ["%a %d", (d) => d.getDay() && d.getDate() != 1],
    ["%b %d", (d) => d.getDate() != 1; }],
    ["%B",    (d) => d.getMonth()],
    ["%Y",    ()  => true]
 ]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickFormat(axisTimeFormat);

Installation

$ npm install -save d3
                    d3-scale
                    d3-array
                    d3-shape
                    d3-interpolate
                    d3-color
                    d3-time-format
                    d3-ease
                    d3-transition
                    d3-selection
                    d3-time
                    d3-path
                    d3-dispatch
                    d3-format
                    d3-timer
                    d3-drag
                    d3-geo
                    d3-brush
                    d3-dsv
                    d3-random