One place for hosting & domains

      Tutorial: How to Use Synology’s Hyper Backup App to Sync with DreamObjects


      If you’ve got a NAS (network attached storage) device at home, you know how great they are.

      These mini-computers full of hard drives live on your home network, ready and waiting to store and share all of your music, photos, and other media. You can grant access to yourself, your friends, your family, or everyone on the planet using any number of different apps!

      While NAS devices themselves are extremely fault-tolerant, that doesn’t mean that they could survive . . . a flood. Or a fire. A Godzilla foot would definitely destroy all your data.

      That’s why you should back up your NAS regularly. Synology has made this easy by providing powerful backup tools in their line of NAS devices.

      Today we’ll look at setting up Synology’s Hyper Backup app, and we’ll be using it to back up the contents of our Synology device to DreamHost’s DreamObjects cloud storage service.

      You’ll first need to create a private “bucket” on DreamObjects this where your data will live. Doing this is quick and easy, and at the end of the process, you’ll have three things: a public key, a secret key, and a bucket name.

      Secure Cloud Storage Hosting

      DreamObjects is an inexpensive object storage service great for hosting files, storing backups, and Web app development.

      The process to create a bucket on the DreamObjects control panel is pretty straightforward, but if you need a little hand-holding, our knowledge base has got you covered: DreamHost Knowledge Base: What is DreamObjects?

      Once you’ve got your bucket created, log in to your Synology device’s web interface, also known as DSM.

      Click over to the Package Center, navigate to the “Hyper Backup” app, and click to Install/Open it.

      Synology Hyper Backup

      If you’ve just installed Hyper Backup, navigate to your list of installed apps where you’ll see that it now sits proudly under a spotlight, awaiting your click.

      Hyper Backup App Icon

      Launch Hyper Backup.

      The first thing you’ll be asked is which where you’d like your backups to live. Navigate to “S3 Storage.”

      S3 Storage

      Why S3? Simple! DreamObjects is compatible with S3’s API, meaning that just about any app written for AWS S3 will work flawlessly with DreamObjects.

      Backup Destination Settings

      Select “Custom Server URL” for your S3 Server. Your server address should be “objects-us-east1.dream.io” and your signature version can be v4. Once you provide your access key (public key) and secret key, all of your available DreamObjects buckets will populate the dropdown list. Select the one that you want to use (you might only have one.) The “Directory” will be a directory that lives at the top level of your bucket, and you can name it whatever you like.

      Now you’ll need to tell Hyper Backup which directories to include in this backup task.

      Selecting directories to backup

      If you want to back up the settings and data from any of Synology’s own applications, you can do that as well on the next screen.

      Selecting apps to backup

      You’ll now need to give this backup task a name so that you’ll be able to quickly identify it in the future. You’ll also need to choose how often you’d like the backup to run.

      Configure your backup

      Finally, you’ll need to determine your rotation settings. This is important. If you allow your backups to run every week, for example, you’ll end up with 52 backups by the end of the year, and you’ll end up paying for all 52 of them. That’s probably not what you want.

      What you select on this screen is up to you, but here’s the official word from Synology on how their Rotation Settings work.

      Rotation Settings

      If all goes as planned, you should now see a screen like this:

      Backups have begun

      Whatever data you’ve selected from your NAS is now being backed up to DreamObjects! You can now kick back and never worry about having to retrieve it until you have to!

      If all goes well, you’ll see this:

      Great success!

      If you ever have a need to restore your data, just launch Hyper Backup and click this little fella to get started:

      Restore Data

      Alternately, you can use Synology’s standalone utility, Hyper Backup Explorer to retrieve individual files buried within your backups as well.

      Backups automated with tools like Hyper Backup and DreamObjects can be key to ensuring the security of your data in a world full of ransomware, random hardware failures, and natural disasters. Be prepared!



      Source link

      How to Set Up a Scalable Django App with DigitalOcean Managed Databases and Spaces


      Introduction

      Django is a powerful web framework that can help you get your Python application or website off the ground quickly. It includes several convenient features like an object-relational mapper, a Python API, and a customizable administrative interface for your application. It also includes a caching framework and encourages clean app design through its URL Dispatcher and Template system.

      Out of the box, Django includes a minimal web server for testing and local development, but it should be paired with a more robust serving infrastructure for production use cases. Django is often rolled out with an Nginx web server to handle static file requests and HTTPS redirection, and a Gunicorn WSGI server to serve the app.

      In this guide, we will augment this setup by offloading static files like Javascript and CSS stylesheets to DigitalOcean Spaces, and optionally delivering them using a Content Delivery Network, or CDN, which stores these files closer to end users to reduce transfer times. We’ll also use a DigitalOcean Managed PostgreSQL database as our data store to simplify the data layer and avoid having to manually configure a scalable PostgreSQL database.

      Prerequisites

      Before you begin with this guide, you should have the following available to you:

      Step 1 — Installing Packages from the Ubuntu Repositories

      To begin, we’ll download and install all of the items we need from the Ubuntu repositories. We’ll use the Python package manager pip to install additional components a bit later.

      We need to first update the local apt package index and then download and install the packages.

      In this guide, we’ll use Django with Python 3. To install the necessary libraries, log in to your server and type:

      • sudo apt update
      • sudo apt install python3-pip python3-dev libpq-dev curl postgresql-client

      This will install pip, the Python development files needed to build Gunicorn, the libpq header files needed to build the Pyscopg PostgreSQL Python adapter, and the PostgreSQL command-line client.

      Hit Y and then ENTER when prompted to begin downloading and installing the packages.

      Next, we’ll configure the database to work with our Django app.

      Step 2 — Creating the PostgreSQL Database and User

      We’ll now create a database and database user for our Django application.

      To begin, grab the Connection Parameters for your cluster by navigating to Databases from the Cloud Control Panel, and clicking into your database. You should see a Connection Details box containing some parameters for your cluster. Note these down.

      Back on the command line, log in to your cluster using these credentials and the psql PostgreSQL client we just installed:

      • psql -U do_admin -h host -p port -d database

      When prompted, enter the password displayed alongside the doadmin Postgres username, and hit ENTER.

      You will be given a PostgreSQL prompt from which you can manage the database.

      First, create a database for your project called polls:

      Note: Every Postgres statement must end with a semicolon, so make sure that your command ends with one if you are experiencing issues.

      We can now switch to the polls database:

      Next, create a database user for the project. Make sure to select a secure password:

      • CREATE USER myprojectuser WITH PASSWORD 'password';

      We'll now modify a few of the connection parameters for the user we just created. This will speed up database operations so that the correct values do not have to be queried and set each time a connection is established.

      We are setting the default encoding to UTF-8, which Django expects. We are also setting the default transaction isolation scheme to "read committed", which blocks reads from uncommitted transactions. Lastly, we are setting the timezone. By default, our Django projects will be set to use UTC. These are all recommendations from the Django project itself.

      Enter the following commands at the PostgreSQL prompt:

      • ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
      • ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
      • ALTER ROLE myprojectuser SET timezone TO 'UTC';

      Now we can give our new user access to administer our new database:

      • GRANT ALL PRIVILEGES ON DATABASE polls TO myprojectuser;

      When you are finished, exit out of the PostgreSQL prompt by typing:

      Your Django app is now ready to connect to and manage this database.

      In the next step, we'll install virtualenv and create a Python virtual environment for our Django project.

      Step 3 — Creating a Python Virtual Environment for your Project

      Now that we've set up our database to work with our application, we'll create a Python virtual environment that will isolate this project's dependencies from the system's global Python installation.

      To do this, we first need access to the virtualenv command. We can install this with pip.

      Upgrade pip and install the package by typing:

      • sudo -H pip3 install --upgrade pip
      • sudo -H pip3 install virtualenv

      With virtualenv installed, we can create a directory to store our Python virtual environments and make one to use with the Django polls app.

      Create a directory called envs and navigate into it:

      Within this directory, create a Python virtual environment called polls by typing:

      This will create a directory called polls within the envs directory. Inside, it will install a local version of Python and a local version of pip. We can use this to install and configure an isolated Python environment for our project.

      Before we install our project's Python requirements, we need to activate the virtual environment. You can do that by typing:

      • source polls/bin/activate

      Your prompt should change to indicate that you are now operating within a Python virtual environment. It will look something like this: (polls)user@host:~/envs$.

      With your virtual environment active, install Django, Gunicorn, and the psycopg2 PostgreSQL adaptor with the local instance of pip:

      Note: When the virtual environment is activated (when your prompt has (polls) preceding it), use pip instead of pip3, even if you are using Python 3. The virtual environment's copy of the tool is always named pip, regardless of the Python version.

      • pip install django gunicorn psycopg2-binary

      You should now have all of the software you need to run the Django polls app. In the next step, we'll create a Django project and install this app.

      Step 4 — Creating the Polls Django Application

      We can now set up our sample application. In this tutorial, we'll use the Polls demo application from the Django documentation. It consists of a public site that allows users to view polls and vote in them, and an administrative control panel that allows the admin to modify, create, and delete polls.

      In this guide, we'll skip through the tutorial steps, and simply clone the final application from the DigitalOcean Community django-polls repo.

      If you'd like to complete the steps manually, create a directory called django-polls in your home directory and navigate into it:

      • cd
      • mkdir django-polls
      • cd django-polls

      From there, you can follow the Writing your first Django app tutorial from the official Django documentation. When you're done, skip to Step 5.

      If you just want to clone the finished app, navigate to your home directory and use git to clone the django-polls repo:

      • cd
      • git clone https://github.com/do-community/django-polls.git

      cd into it, and list the directory contents:

      You should see the following objects:

      Output

      LICENSE README.md manage.py mysite polls templates

      manage.py is the main command-line utility used to manipulate the app. polls contains the polls app code, and mysite contains project-scope code and settings. templates contains custom template files for the administrative interface. To learn more about the project structure and files, consult Creating a Project from the official Django documentation.

      Before running the app, we need to adjust its default settings and connect it to our database.

      Step 5 — Adjusting the App Settings

      In this step, we'll modify the Django project's default configuration to increase security, connect Django to our database, and collect static files into a local directory.

      Begin by opening the settings file in your text editor:

      • nano ~/django-polls/mysite/settings.py

      Start by locating the ALLOWED_HOSTS directive. This defines a list of the addresses or domain names that you want to use to connect to the Django instance. An incoming request with a Host header not in this list will raise an exception. Django requires that you set this to prevent a certain class of security vulnerability.

      In the square brackets, list the IP addresses or domain names associated with your Django server. Each item should be listed in quotations with entries separated by a comma. Your list will also include localhost, since you will be proxying connections through a local Nginx instance. If you wish to include requests for an entire domain and any subdomains, prepend a period to the beginning of the entry.

      In the snippet below, there are a few commented out examples that demonstrate what these entries should look like:

      ~/django-polls/mysite/settings.py

      . . .
      
      # The simplest case: just add the domain name(s) and IP addresses of your Django server
      # ALLOWED_HOSTS = [ 'example.com', '203.0.113.5']
      # To respond to 'example.com' and any subdomains, start the domain with a dot
      # ALLOWED_HOSTS = ['.example.com', '203.0.113.5']
      ALLOWED_HOSTS = ['your_server_domain_or_IP', 'second_domain_or_IP', . . ., 'localhost']
      
      . . . 
      

      Next, find the section of the file that configures database access. It will start with DATABASES. The configuration in the file is for a SQLite database. We already created a PostgreSQL database for our project, so we need to adjust these settings.

      We will tell Django to use the psycopg2 database adaptor we installed with pip, instead of the default SQLite engine. We’ll also reuse the Connection Parameters referenced in Step 2. You can always find this information from the Managed Databases section of the DigitalOcean Cloud Control Panel.

      Update the file with your database settings: the database name (polls), the database username, the database user's password, and the database host and port. Be sure to replace the database-specific values with your own information:

      ~/django-polls/mysite/settings.py

      . . .
      
      DATABASES = {
          'default': {
              'ENGINE': 'django.db.backends.postgresql_psycopg2',
              'NAME': 'polls',
              'USER': 'myprojectuser',
              'PASSWORD': 'password',
              'HOST': 'managed_db_host',
              'PORT': 'managed_db_port',
          }
      }
      
      . . .
      

      Next, move down to the bottom of the file and add a setting indicating where the static files should be placed. This is necessary so that Nginx can handle requests for these items. The following line tells Django to place them in a directory called static in the base project directory:

      ~/django-polls/mysite/settings.py

      . . .
      
      STATIC_URL = '/static/'
      STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
      

      Save and close the file when you are finished.

      At this point, you've configured the Django project's database, security, and static files settings. If you followed the polls tutorial from the start and did not clone the GitHub repo, you can move on to Step 6. If you cloned the GitHub repo, there remains one additional step.

      The Django settings file contains a SECRET_KEY variable that is used to create hashes for various Django objects. It's important that it is set to a unique, unpredictable value. The SECRET_KEY variable has been scrubbed from the GitHub repository, so we'll create a new one using a function built-in to the django Python package called get_random_secret_key(). From the command line, open up a Python interpreter:

      You should see the following output and prompt:

      Output

      Python 3.6.7 (default, Oct 22 2018, 11:32:17) [GCC 8.2.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>>

      Import the get_random_secret_key function from the Django package, then call the function:

      • from django.core.management.utils import get_random_secret_key
      • get_random_secret_key()

      Copy the resulting key to your clipboard.

      Exit the Python interpreter by pressing CTRL+D.

      Next, open up the settings file in your text editor once again:

      nano ~/django-polls/mysite/settings.py
      

      Locate the SECRET_KEY variable and paste in the key you just generated:

      ~/django-polls/mysite/settings.py

      . . .
      
      # SECURITY WARNING: keep the secret key used in production secret!
      SECRET_KEY = 'your_secret_key_here'
      
      . . .
      

      Save and close the file.

      We'll now test the app locally using the Django development server to ensure that everything's been correctly configured.

      Step 6 — Testing the App

      Before we run the Django development server, we need to use the manage.py utility to create the database schema and collect static files into the STATIC_ROOT directory.

      Navigate into the project's base directory, and create the initial database schema in our PostgreSQL database using the makemigrations and migrate commands:

      • cd django-polls
      • ./manage.py makemigrations
      • ./manage.py migrate

      makemigrations will create the migrations, or database schema changes, based on the changes made to Django models. migrate will apply these migrations to the database schema. To learn more about migrations in Django, consult Migrations from the official Django documentation.

      Create an administrative user for the project by typing:

      • ./manage.py createsuperuser

      You will have to select a username, provide an email address, and choose and confirm a password.

      We can collect all of the static content into the directory location we configured by typing:

      • ./manage.py collectstatic

      The static files will then be placed in a directory called static within your project directory.

      If you followed the initial server setup guide, you should have a UFW firewall protecting your server. In order to test the development server, we'll have to allow access to the port we'll be using.

      Create an exception for port 8000 by typing:

      Testing the App Using the Django Development Server

      Finally, you can test your project by starting the Django development server with this command:

      • ./manage.py runserver 0.0.0.0:8000

      In your web browser, visit your server's domain name or IP address followed by :8000 and the polls path:

      • http://server_domain_or_IP:8000/polls

      You should see the Polls app interface:

      Polls App Interface

      To check out the admin interface, visit your server's domain name or IP address followed by :8000 and the administrative interface's path:

      • http://server_domain_or_IP:8000/admin

      You should see the Polls app admin authentication window:

      Polls Admin Auth Page

      Enter the administrative username and password you created with the createsuperuser command.

      After authenticating, you can access the Polls app's administrative interface:

      Polls Admin Main Interface

      When you are finished exploring, hit CTRL-C in the terminal window to shut down the development server.

      Testing the App Using Gunicorn

      The last thing we want to do before offloading static files is test Gunicorn to make sure that it can serve the application. We can do this by entering our project directory and using gunicorn to load the project's WSGI module:

      • gunicorn --bind 0.0.0.0:8000 mysite.wsgi

      This will start Gunicorn on the same interface that the Django development server was running on. You can go back and test the app again.

      Note: The admin interface will not have any of the styling applied since Gunicorn does not know how to find the static CSS content responsible for this.

      We passed Gunicorn a module by specifying the relative directory path to Django's wsgi.py file, the entry point to our application,. This file defines a function called application, which communicates with the application. To learn more about the WSGI specification, click here.

      When you are finished testing, hit CTRL-C in the terminal window to stop Gunicorn.

      We'll now offload the application’s static files to DigitalOcean Spaces.

      Step 7 — Offloading Static Files to DigitalOcean Spaces

      At this point, Gunicorn can serve our Django application but not its static files. Usually we'd configure Nginx to serve these files, but in this tutorial we'll offload them to DigitalOcean Spaces using the django-storages plugin. This allows you to easily scale Django by centralizing its static content and freeing up server resources. In addition, you can deliver this static content using the DigitalOcean Spaces CDN.

      For a full guide on offloading Django static files to Object storage, consult How to Set Up Object Storage with Django.

      Installing and Configuring django-storages

      We'll begin by installing the django-storages Python package. The django-storages package provides Django with the S3Boto3Storage storage backend that uses the boto3 library to upload files to any S3-compatible object storage service.

      To start, install thedjango-storages and boto3 Python packages using pip:

      • pip install django-storages boto3

      Next, open your app's Django settings file again:

      • nano ~/django-polls/mysite/settings.py

      Navigate down to the INSTALLED_APPS section of the file, and append storages to the list of installed apps:

      ~/django-polls/mysite/settings.py

      . . .
      
      INSTALLED_APPS = [
          . . .
          'django.contrib.staticfiles',
          'storages',
      ]
      
      . . .
      

      Scroll further down the file to the STATIC_URL we previously modified. We'll now overwrite these values and append new S3Boto3Storage backend parameters. Delete the code you entered earlier, and add the following blocks, which include access and location information for your Space. Remember to replace the highlighted values here with your own information::

      ~/django-polls/mysite/settings.py

      . . .
      
      # Static files (CSS, JavaScript, Images)
      # https://docs.djangoproject.com/en/2.1/howto/static-files/
      
      AWS_ACCESS_KEY_ID = 'your_spaces_access_key'
      AWS_SECRET_ACCESS_KEY = 'your_spaces_secret_key'
      
      AWS_STORAGE_BUCKET_NAME = 'your_space_name'
      AWS_S3_ENDPOINT_URL = 'spaces_endpoint_URL'
      AWS_S3_OBJECT_PARAMETERS = {
          'CacheControl': 'max-age=86400',
      }
      AWS_LOCATION = 'static'
      AWS_DEFAULT_ACL = 'public-read'
      
      STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
      
      STATIC_URL = '{}/{}/'.format(AWS_S3_ENDPOINT_URL, AWS_LOCATION)
      STATIC_ROOT = 'static/'
      

      We define the following configuration items:

      • AWS_ACCESS_KEY_ID: The Access Key ID for the Space, which you created in the tutorial prerequisites. If you didn’t create a set of Access Keys, consult Sharing Access to Spaces with Access Keys.
      • AWS_SECRET_ACCESS_KEY: The secret key for the DigitalOcean Space.
      • AWS_STORAGE_BUCKET_NAME: Your DigitalOcean Space name.
      • AWS_S3_ENDPOINT_URL : The endpoint URL used to access the object storage service. For DigitalOcean, this will be something like https://nyc3.digitaloceanspaces.com depending on the Space region.
      • AWS_S3_OBJECT_PARAMETERS Sets the cache control headers on static files.
      • AWS_LOCATION: Defines a directory within the object storage bucket where all static files will be placed.
      • AWS_DEFAULT_ACL: Defines the access control list (ACL) for the static files. Setting it to public-read ensures that the files are publicly accessible to end users.
      • STATICFILES_STORAGE: Sets the storage backend Django will use to offload static files. This backend should work with any S3-compatible backend, including DigitalOcean Spaces.
      • STATIC_URL: Specifies the base URL that Django should use when generating URLs for static files. Here, we combine the endpoint URL and the static files subdirectory to construct a base URL for static files.
      • STATIC_ROOT: Specifies where to collect static files locally before copying them to object storage.

      From now on, when you run collectstatic, Django will upload your app's static files to the Space. When you start Django, it'll begin serving static assets like CSS and Javascript from this Space.

      Before we test that this is all functioning correctly, we need to configure Cross-Origin Resource Sharing (CORS) headers for our Spaces files or access to certain static assets may be denied by your web browser.

      CORS headers tell the web browser that the an application running at one domain can access scripts or resources located at another. In this case, we need to allow cross-origin resource sharing for our Django server's domain so that requests for static files in the Space are not denied by the web browser.

      To begin, navigate to the Settings page of your Space using the Cloud Control Panel:

      Screenshot of the Settings tab

      In the CORS Configurations section, click Add.

      CORS advanced settings

      Here, under Origin, enter the wildcard origin, *

      Warning: When you deploy your app into production, be sure to change this value to your exact origin domain (including the http:// or https:// protocol). Leaving this as the wildcard origin is insecure, and we do this here only for testing purposes since setting the origin to http://example.com:8000 (using a nonstandard port) is currently not supported.

      Under Allowed Methods, select GET.

      Click on Add Header, and in text box that appears, enter Access-Control-Allow-Origin.

      Set Access Control Max Age to 600 so that the header we just created expires every 10 minutes.

      Click Save Options.

      From now on, objects in your Space will contain the appropriate Access-Control-Allow-Origin response headers, allowing modern secure web browsers to fetch these files across domains.

      At this point, you can optionally enable the CDN for your Space, which will serve these static files from a distributed network of edge servers. To learn more about CDNs, consult Using a CDN to Speed Up Static Content Delivery. This can significantly improve web performance. If you don't want to enable the CDN for your Space, skip ahead to the next section, Testing Spaces Static File Delivery.

      Enabling CDN (Optional)

      To activate static file delivery via the DigitalOcean Spaces CDN, begin by enabling the CDN for your DigitalOcean Space. To learn how to do this, consult How to Enable the Spaces CDN from the DigitalOcean product documentation.

      Once you've enabled the CDN for your Space, navigate to it using the Cloud Control Panel. You should see a new Endpoints link under your Space name:

      List of Space Endpoints

      These endpoints should contain your Space name.

      Notice the addition of a new Edge endpoint. This endpoint routes requests for Spaces objects through the CDN, serving them from the edge cache as much as possible. Note down this Edge endpoint, as we'll use it to configure the django-storages plugin.

      Next, edit your app's Django settings file once again:

      • nano ~/django-polls/mysite/settings.py

      Navigate down to the Static Files section we recently modified. Add the AWS_S3_CUSTOM_DOMAIN parameter to configure the django-storages plugin CDN endpoint and update the STATIC_URL parameter to use this new CDN endpoint:

      ~/django-polls/mysite/settings.py

      . . .
      
      # Static files (CSS, JavaScript, Images)
      # https://docs.djangoproject.com/en/2.1/howto/static-files/
      
      # Moving static assets to DigitalOcean Spaces as per:
      # https://www.digitalocean.com/community/tutorials/how-to-set-up-object-storage-with-django
      AWS_ACCESS_KEY_ID = 'your_spaces_access_key'
      AWS_SECRET_ACCESS_KEY = 'your_spaces_secret_key'
      
      AWS_STORAGE_BUCKET_NAME = 'your_space_name'
      AWS_S3_ENDPOINT_URL = 'spaces_endpoint_URL'
      AWS_S3_CUSTOM_DOMAIN = 'spaces_edge_endpoint_URL'
      AWS_S3_OBJECT_PARAMETERS = {
          'CacheControl': 'max-age=86400',
      }
      AWS_LOCATION = 'static'
      AWS_DEFAULT_ACL = 'public-read'
      
      STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
      
      STATIC_URL = '{}/{}/'.format(AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
      STATIC_ROOT = 'static/'
      

      Here, replace the spaces_edge_endpoint_URL with the Edge endpoint you just noted down, truncating the https:// prefix. For example, if the Edge endpoint URL is https://example.sfo2.cdn.digitaloceanspaces.com, AWS_S3_CUSTOM_DOMAIN should be set to example.sfo2.cdn.digitaloceanspaces.com.

      When you're done, save and close the file.

      When you start Django, it will now serve static content using the CDN for your DigitalOcean Space.

      Testing Spaces Static File Delivery

      We'll now test that Django is correctly serving static files from our DigitalOcean Space.

      Navigate to your Django app directory:

      From here, run collectstatic to collect and upload static files to your DigitalOcean Space:

      • python manage.py collectstatic

      You should see the following output:

      Output

      You have requested to collect static files at the destination location as specified in your settings. This will overwrite existing files! Are you sure you want to do this? Type 'yes' to continue, or 'no' to cancel:

      Type yes and hit ENTER to confirm.

      You should then see output like the following

      Output

      121 static files copied.

      This confirms that Django successfully uploaded the polls app static files to your Space. You can navigate to your Space using the Cloud Control Panel, and inspect the files in the static directory.

      Next, we'll verify that Django is rewriting the appropriate URLs.

      Start the Gunicorn server:

      • gunicorn --bind 0.0.0.0:8000 mysite.wsgi

      In your web browser, visit your server's domain name or IP address followed by :8000 and /admin:

      http://server_domain_or_IP:8000/admin
      

      You should once again see the Polls app admin authentication window, this time with correct styling.

      Now, use your browser's developer tools to inspect the page contents and reveal the source file storage locations.

      To do this using Google Chrome, right-click the page, and select Inspect.

      You should see the following window:

      Chrome Dev Tools Window

      From here, click on Sources in the toolbar. In the list of source files in the left-hand pane, you should see /admin/login under your Django server's domain, and static/admin under your Space's CDN endpoint. Within static/admin, you should see both the css and fonts directories.

      This confirms that CSS stylesheets and fonts are correctly being served from your Space's CDN.

      When you are finished testing, hit CTRL-C in the terminal window to stop Gunicorn.

      You can disable your active Python virtual environment by entering deactivate:

      Your prompt should return to normal.

      At this point you've successfully offloaded static files from your Django server, and are serving them from object storage. We can now move on to configuring Gunicorn to start automatically as a system service.

      Step 8 — Creating systemd Socket and Service Files for Gunicorn

      In Step 6 we tested that Gunicorn can interact with our Django application, but we should implement a more robust way of starting and stopping the application server. To accomplish this, we'll make systemd service and socket files.

      The Gunicorn socket will be created at boot and will listen for connections. When a connection occurs, systemd will automatically start the Gunicorn process to handle the connection.

      Start by creating and opening a systemd socket file for Gunicorn with sudo privileges:

      • sudo nano /etc/systemd/system/gunicorn.socket

      Inside, we will create a [Unit] section to describe the socket, a [Socket] section to define the socket location, and an [Install] section to make sure the socket is created at the right time. Add the following code to the file:

      /etc/systemd/system/gunicorn.socket

      [Unit]
      Description=gunicorn socket
      
      [Socket]
      ListenStream=/run/gunicorn.sock
      
      [Install]
      WantedBy=sockets.target
      

      Save and close the file when you are finished.

      Next, create and open a systemd service file for Gunicorn with sudo privileges in your text editor. The service filename should match the socket filename with the exception of the extension:

      • sudo nano /etc/systemd/system/gunicorn.service

      Start with the [Unit] section, which specifies metadata and dependencies. We'll put a description of our service here and tell the init system to only start this after the networking target has been reached. Because our service relies on the socket from the socket file, we need to include a Requires directive to indicate that relationship:

      /etc/systemd/system/gunicorn.service

      [Unit]
      Description=gunicorn daemon
      Requires=gunicorn.socket
      After=network.target
      

      Next, we'll open up the [Service] section. We'll specify the user and group that we want to process to run under. We will give our regular user account ownership of the process since it owns all of the relevant files. We'll give group ownership to the www-data group so that Nginx can communicate easily with Gunicorn.

      We'll then map out the working directory and specify the command to use to start the service. In this case, we'll have to specify the full path to the Gunicorn executable, which is installed within our virtual environment. We will bind the process to the Unix socket we created within the /run directory so that the process can communicate with Nginx. We log all data to standard output so that the journald process can collect the Gunicorn logs. We can also specify any optional Gunicorn tweaks here, like the number of worker processes. Here, we run Gunicorn with 3 worker processes.

      Add the following Service section to the file. Be sure to replace the username listed here with your own username:

      /etc/systemd/system/gunicorn.service

      [Unit]
      Description=gunicorn daemon
      Requires=gunicorn.socket
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/django-polls
      ExecStart=/home/sammy/envs/polls/bin/gunicorn 
                --access-logfile - 
                --workers 3 
                --bind unix:/run/gunicorn.sock 
                mysite.wsgi:application
      

      Finally, we'll add an [Install] section. This will tell systemd what to link this service to if we enable it to start at boot. We want this service to start when the regular multi-user system is up and running:

      /etc/systemd/system/gunicorn.service

      [Unit]
      Description=gunicorn daemon
      Requires=gunicorn.socket
      After=network.target
      
      [Service]
      User=sammy
      Group=www-data
      WorkingDirectory=/home/sammy/django-polls
      ExecStart=/home/sammy/envs/polls/bin/gunicorn 
                --access-logfile - 
                --workers 3 
                --bind unix:/run/gunicorn.sock 
                mysite.wsgi:application
      
      [Install]
      WantedBy=multi-user.target
      

      With that, our systemd service file is complete. Save and close it now.

      We can now start and enable the Gunicorn socket. This will create the socket file at /run/gunicorn.sock now and at boot. When a connection is made to that socket, systemd will automatically start the gunicorn.service to handle it:

      • sudo systemctl start gunicorn.socket
      • sudo systemctl enable gunicorn.socket

      We can confirm that the operation was successful by checking for the socket file.

      Checking for the Gunicorn Socket File

      Check the status of the process to find out whether it started successfully:

      • sudo systemctl status gunicorn.socket

      You should see the following output:

      Output

      Failed to dump process list, ignoring: No such file or directory ● gunicorn.socket - gunicorn socket Loaded: loaded (/etc/systemd/system/gunicorn.socket; enabled; vendor preset: enabled) Active: active (running) since Tue 2019-03-05 19:19:16 UTC; 1h 22min ago Listen: /run/gunicorn.sock (Stream) CGroup: /system.slice/gunicorn.socket Mar 05 19:19:16 django systemd[1]: Listening on gunicorn socket.

      Next, check for the existence of the gunicorn.sock file within the /run directory:

      Output

      /run/gunicorn.sock: socket

      If the systemctl status command indicated that an error occurred, or if you do not find the gunicorn.sock file in the directory, it's an indication that the Gunicorn socket was not created correctly. Check the Gunicorn socket's logs by typing:

      • sudo journalctl -u gunicorn.socket

      Take another look at your /etc/systemd/system/gunicorn.socket file to fix any problems before continuing.

      Testing Socket Activation

      Currently, if you've only started the gunicorn.socket unit, the gunicorn.service will not be active, since the socket has not yet received any connections. You can check this by typing:

      • sudo systemctl status gunicorn

      Output

      ● gunicorn.service - gunicorn daemon Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled) Active: inactive (dead)

      To test the socket activation mechanism, we can send a connection to the socket through curl by typing:

      • curl --unix-socket /run/gunicorn.sock localhost

      You should see the HTML output from your application in the terminal. This indicates that Gunicorn has started and is able to serve your Django application. You can verify that the Gunicorn service is running by typing:

      • sudo systemctl status gunicorn

      Output

      ● gunicorn.service - gunicorn daemon Loaded: loaded (/etc/systemd/system/gunicorn.service; disabled; vendor preset: enabled) Active: active (running) since Tue 2019-03-05 20:43:56 UTC; 1s ago Main PID: 19074 (gunicorn) Tasks: 4 (limit: 4915) CGroup: /system.slice/gunicorn.service ├─19074 /home/sammy/envs/polls/bin/python3 /home/sammy/envs/polls/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/gunicorn.sock mysite.wsgi:application ├─19098 /home/sammy/envs/polls/bin/python3 /home/sammy/envs/polls/bin/gunicorn . . . Mar 05 20:43:56 django systemd[1]: Started gunicorn daemon. Mar 05 20:43:56 django gunicorn[19074]: [2019-03-05 20:43:56 +0000] [19074] [INFO] Starting gunicorn 19.9.0 . . . Mar 05 20:44:15 django gunicorn[19074]: - - [05/Mar/2019:20:44:15 +0000] "GET / HTTP/1.1" 301 0 "-" "curl/7.58.0"

      If the output from curl or the output of systemctl status indicates that a problem occurred, check the logs for additional details:

      • sudo journalctl -u gunicorn

      You can also check your /etc/systemd/system/gunicorn.service file for problems. If you make changes to this file, be sure to reload the daemon to reread the service definition and restart the Gunicorn process:

      • sudo systemctl daemon-reload
      • sudo systemctl restart gunicorn

      Make sure you troubleshoot any issues before continuing on to configuring the Nginx server.

      Step 8 — Configuring Nginx HTTPS and Gunicorn Proxy Passing

      Now that Gunicorn is set up in a more robust fashion, we need to configure Nginx to encrypt connections and hand off traffic to the Gunicorn process.

      If you followed the preqrequisites and set up Nginx with Let's Encrypt, you should already have a server block file corresponding to your domain available to you in Nginx's sites-available directory. If not, follow How To Secure Nginx with Let's Encrypt on Ubuntu 18.04 and return to this step.

      Before we edit this example.com server block file, we’ll first remove the default server block file that gets rolled out by default after installing Nginx:

      • sudo rm /etc/nginx/sites-enabled/default

      We'll now modify the example.com server block file to pass traffic to Gunicorn instead of the default index.html page configured in the prerequisite step.

      Open the server block file corresponding to your domain in your editor:

      • sudo nano /etc/nginx/sites-available/example.com

      You should see something like the following:

      /etc/nginx/sites-available/example.com

      server {
      
              root /var/www/example.com/html;
              index index.html index.htm index.nginx-debian.html;
      
              server_name example.com www.example.com;
      
              location / {
                      try_files $uri $uri/ =404;
              }
      
          listen [::]:443 ssl ipv6only=on; # managed by Certbot
          listen 443 ssl; # managed by Certbot
          ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # managed by Certbot
          ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # managed by Certbot
          include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
          ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
      
      }
      
      server {
          if ($host = example.com) {
              return 301 https://$host$request_uri;
          } # managed by Certbot
      
      
              listen 80;
              listen [::]:80;
      
              server_name example.com www.example.com;
          return 404; # managed by Certbot
      
      
      }
      

      This is a combination of the default server block file created in How to Install Nginx on Ubuntu 18.04 as well as additions appended automatically by Let's Encrypt. We are going to delete the contents of this file and write a new configuration that redirects HTTP traffic to HTTPS, and forwards incoming requests to the Gunicorn socket we created in the previous step.

      If you'd like, you can make a backup of this file using cp. Quit your text editor and create a backup called example.com.old:

      • sudo cp /etc/nginx/sites-available/example.com /etc/nginx/sites-available/example.com.old

      Now, reopen the file and delete its contents. We'll build the new configuration block by block.

      Begin by pasting in the following block, which redirects HTTP requests at port 80 to HTTPS:

      /etc/nginx/sites-available/example.com

      server {
          listen 80 default_server;
          listen [::]:80 default_server;
          server_name _;
          return 301 https://example.com$request_uri;
      }
      

      Here we listen for HTTP IPv4 and IPv6 requests on port 80 and send a 301 response header to redirect the request to HTTPS port 443 using the example.com domain. This will also redirect direct HTTP requests to the server’s IP address.

      After this block, append the following block of config code that handles HTTPS requests for the example.com domain:

      /etc/nginx/sites-available/example.com

      . . . 
      server {
          listen [::]:443 ssl ipv6only=on;
          listen 443 ssl;
          server_name example.com www.example.com;
      
          # Let's Encrypt parameters
          ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
          ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
          include /etc/letsencrypt/options-ssl-nginx.conf;
          ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
      
          location = /favicon.ico { access_log off; log_not_found off; }
      
          location / {
              proxy_pass         http://unix:/run/gunicorn.sock;
              proxy_redirect     off;
      
              proxy_set_header   Host              $http_host;
              proxy_set_header   X-Real-IP         $remote_addr;
              proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
              proxy_set_header   X-Forwarded-Proto https;
          }
      }
      

      Here, we first listen on port 443 for requests hitting the example.com and www.example.com domains.

      Next, we provide the same Let's Encrypt configuration included in the default server block file, which specifies the location of the SSL certificate and private key, as well as some additional security parameters.

      The location = /favicon.ico line instructs Nginx to ignore any problems with finding a favicon.

      The last location = / block instructs Nginx to hand off requests to the Gunicorn socket configured in Step 8. In addition, it adds headers to inform the upstream Django server that a request has been forwarded and to provide it with various request properties.

      After you've pasted in those two configuration blocks, the final file should look something like this:

      /etc/nginx/sites-available/example.com

      server {
          listen 80 default_server;
          listen [::]:80 default_server;
          server_name _;
          return 301 https://example.com$request_uri;
      }
      server {
              listen [::]:443 ssl ipv6only=on;
              listen 443 ssl;
              server_name example.com www.example.com;
      
              # Let's Encrypt parameters
              ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
              ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
              include /etc/letsencrypt/options-ssl-nginx.conf;
              ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
      
              location = /favicon.ico { access_log off; log_not_found off; }
      
              location / {
                proxy_pass         http://unix:/run/gunicorn.sock;
                proxy_redirect     off;
      
                proxy_set_header   Host              $http_host;
                proxy_set_header   X-Real-IP         $remote_addr;
                proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
                proxy_set_header   X-Forwarded-Proto https;
              }
      }
      

      Save and close the file when you are finished.

      Test your Nginx configuration for syntax errors by typing:

      If your configuration is error-free, restart Nginx by typing:

      • sudo systemctl restart nginx

      You should now be able to visit your server's domain or IP address to view your application. Your browser should be using a secure HTTPS connection to connect to the Django backend.

      To completely secure our Django project, we need to add a couple of security parameters to its settings.py file. Reopen this file in your editor:

      • nano ~/django-polls/mysite/settings.py

      Scroll to the bottom of the file, and add the following parameters:

      ~/django-polls/mysite/settings.py

      . . .
      
      SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
      SESSION_COOKIE_SECURE = True
      CSRF_COOKIE_SECURE = True
      SECURE_SSL_REDIRECT = True
      

      These settings tell Django that you have enabled HTTPS on your server, and instruct it to use "secure" cookies. To learn more about these settings, consult the SSL/HTTPS section of Security in Django.

      When you're done, save and close the file.

      Finally, restart Gunicorn:

      • sudo systemctl restart gunicorn

      At this point, you have configured Nginx to redirect HTTP requests and hand off these requests to Gunicorn. HTTPS should now be fully enabled for your Django project and app. If you're running into errors, this discussion on troubleshooting Nginx and Gunicorn may help.

      Warning: As stated in Configuring CORS Headers, be sure to change the Origin from the wildcard * domain to your domain name (https://example.com in this guide) before making your app accessible to end users.

      Conclusion

      In this guide, you set up and configured a scalable Django application running on an Ubuntu 18.04 server. This setup can be replicated across multiple servers to create a highly-available architecture. Furthermore, this app and its config can be containerized using Docker or another container runtime to ease deployment and scaling. These containers can then be deployed into a container cluster like Kubernetes. In an upcoming Tutorial series, we will explore how to containerize and modernize this Django polls app so that it can run in a Kubernetes cluster.

      In addition to static files, you may also wish to offload your Django Media files to object storage. To learn how to do this, consult Using Amazon S3 to Store your Django Site's Static and Media Files. You might also consider compressing static files to further optimize their delivery to end users. To do this, you can use a Django plugin like Django compressor.



      Source link

      How To Build a Weather App with Angular, Bootstrap, and the APIXU API


      The author selected NPower to receive a donation as part of the Write for DOnations program.

      Introduction

      Angular is a front-end web framework built by Google. It allows developers to build single-page applications modeled around a model-view-controller (MVC) or model-view-viewmodel (MVVM) software architectural pattern. This architecture divides applications into different, but connected parts allowing for parallel development. Following this pattern, Angular splits its different components into the respective parts of a web application. Its components manage the data and logic that pertain to that component, display the data in its respective view, and adapts or controls the view based on the different messages that it receives from the rest of the app.

      Bootstrap is a front-end library that helps developers build responsive websites (sites that adapt to different devices), quickly and effectively. It makes use of a grid system that divides each page into twelve columns, which ensures that the page maintains its correct size and scale no matter what device it’s being viewed on.

      APIXU provides global weather data to users via their API. Using APIXU, a user can retrieve the latest weather as well as future weather forecasts for any location in the world.

      In this tutorial, you’ll create a weather app using Angular, Bootstrap, and the APIXU API. You’ll be able to type a location into a search form and on submission of that form, see the current weather details for that location displayed in your app. The Angular version used in this tutorial is 7.2.0 and the Bootstrap version used is 4.2.1.

      Prerequisites

      Before you begin this tutorial, you’ll need the following:

      Step 1 — Installing Angular

      Before you begin creating your app, you need to install Angular. Open your terminal and run the following command to install the Angular CLI globally on your machine:

      The Angular CLI is the Command Line Interface for Angular. It serves as the main way to create a new Angular project as well as the different sub-elements that make up an Angular project. Using the -g argument will install it globally.

      After a short while, you'll see the following output:

      Output from installing Angular

      ...
      + @angular/cli@7.2.2
      ...
      

      You've now installed Angular on your local machine. Next, you'll create your Angular application.

      Step 2 — Creating Your Angular App

      In this step you'll create and configure your new Angular application, install all its dependencies, such as Bootstrap and jQuery, and then finally check that the default application is working as expected.

      First, use the ng command to create an Angular application, you can run this from your terminal.

      Note: If you're on Windows, you may have issues trying to run an ng command from Command Prompt even though you've installed Node.js and npm correctly. For example, you may get an error such as: ng is not recognized as an internal or external command. In order to resolve this, please run the ng command inside the installed Node.js command prompt located in the Node.js folder on Windows.

      The ng command is a prerequisite to running any action with Angular from the command line. For example, whether you're building a new project, creating components, or creating tests, you prefix each desired functionality with the ng command. In this tutorial, you'll want to create a new application; you'll achieve this by executing the ng new command. The ng new command creates a new Angular application, imports the necessary libraries, and creates all the default code scaffolding that your application requires.

      Begin by creating a new application, in this tutorial it will be called weather-app, but you can change the name as you wish:

      The ng new command will prompt you for additional information about features that you want to add to your new application.

      Output

      Would you like to add Angular routing? (y/N)

      The Angular routing allows you to build single page applications with different views using the routes and components. Go ahead and type y or hit ENTER to accept the defaults.

      Output

      Which stylesheet format would you like to use? (Use arrow keys)

      Hit ENTER to accept the default CSS option.

      The app will continue its creation process, and after a short time you'll see the following message:

      Output

      ... CREATE weather-app/e2e/src/app.e2e-spec.ts (623 bytes) CREATE weather-app/e2e/src/app.po.ts (204 bytes) ... Successfully initialized git.

      Next, in your text editor, open the weather-app folder.

      Looking at the structure of your directory, you'll see several different folders and files. You can read a full explanation of what all of these files do here, but for the purposes of this tutorial, these are the most important files to understand:

      • The package.json file. Located in the root weather-app folder, it performs just like any other Node.js application, holding all the libraries your application will use, the name of your application, commands to run when testing, and so on. Primarily, this file holds details about external libraries that your Angular application needs in order to run properly.

      • The app.module.ts file. Located in the app folder within the weather-app/src folder, this file tells Angular how to assemble your application and holds details about the components, modules, and providers in your application. You'll already have an imported module, BrowserModule, within your imports array. The BrowserModule provides essential services and directives for your application and should always be the first imported module in your imports array.

      • The angular.json file. Located in the root weather-app folder of your app, this is the configuration file for the Angular CLI. This file holds internal configuration settings of what your Angular application needs to run. It sets defaults for your entire application, and has options such as what configuration files to use when testing, what global styles to use in your app, or to which folder to output your build files. You can find out more about these options in the official Angular-CLI documentation.

      You can leave all of these files alone for the moment, as you'll install Bootstrap next.

      Bootstrap has two dependencies that you'll need to install in order for it to work properly in Angular — jQuery and popper.js. jQuery is a JavaScript library focused on client-side scripting, while popper.js is a positioning library that mainly manages tooltips and popovers.

      In your terminal, move to your root weather-app directory:

      Then execute the following command to install all of the dependencies and save the references to the package.json file:

      • npm install --save jquery popper.js bootstrap

      The --save option automatically imports your references into the package.json file so that you don't have to manually add them after installation.

      You'll see output showing the version numbers that were installed, like the following:

      Output

      + popper.js@1.14.6 + bootstrap@4.2.1 + jquery@3.3.1 ...

      You have now successfully installed Bootstrap and its dependencies. However, you'll also need to include these libraries inside your application. Your weather-app does not yet know that it'll need these libraries, therefore you need to add the paths to jquery, popper.js, bootstrap.js, and bootstrap.css into your angular.json file.

      For popper.js, the file you'll need to include is node_modules/popper.js/dist/umd/popper.js. jQuery requires the node_modules/jquery/dist/jquery.slim.js file. Finally, for Bootstrap you'll need two files (both the JavaScript file and the CSS file). These are node_modules/bootstrap/dist/js/bootstrap.js and node_modules/bootstrap/dist/css/bootstrap.css respectively.

      Now that you have all the required file paths, open the angular.json file in your text editor. The styles array is where you'll add the reference to the CSS files, whilst the scripts array will reference all the scripts. You'll find both of these arrays near the top of the angular.json file, within the "options": JSON object. Add the following highlighted content to the file:

      angular.json

      ...
      "options:" {
      ...
      "styles": [
          "node_modules/bootstrap/dist/css/bootstrap.css",
           "src/styles.css"
      ],
      "scripts": [
          "node_modules/jquery/dist/jquery.slim.js",
          "node_modules/popper.js/dist/umd/popper.js",
          "node_modules/bootstrap/dist/js/bootstrap.js"
      ]},
      ...
      

      You've now imported the main .js and .css files you need for Bootstrap to work properly. You've specified the relative paths to these files from your angular.json file: adding your .css files in the styles array and .js files in the scripts array of angular.json. Make sure you've saved the angular.json file after adding this content.

      Now, start your application with the ng serve command to check that everything is working correctly. From the weather-app directory in your terminal, run:

      The --o argument will automatically open up a browser window that will show your application. The application will take a few seconds to build, and then will display in your browser.

      You'll see the following output in your terminal:

      Output

      ** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ ** ...

      Once the browser opens, you'll see a default Angular app page.

      Image of default created app in Angular

      If you don't see these outputs, run through this step again and ensure that everything is correct. If you see an error such as: Port 4200 is already in use. Use '--port' to specify a different port then you can change the port number by typing:

      • ng serve --o --port <different-port-number>

      The reason for this potential error message is because port 4200 on your machine is being used by another program or process. You can either, if you know what that process is, terminate it or you can follow the above step to specify a different port number.

      You've now set up your application scaffolding. Next, you'll create a weather component that will contain the main form and associated weather details of the search location.

      Step 3 — Creating Your Weather Component

      An Angular application is primarily made up of components, which are pieces of logic that have a particular function within an application. The component is composed of some logic that manages part of the screen in an application — this is called the view.

      For example in this tutorial, you're going to create a Weather Component that will be responsible for handling two tasks:

      • Searching for a location
      • Displaying associated weather data for that location

      To achieve the first objective, you'll create a form that will allow you to search for a location. When you click the search button on your form, it will trigger a function that will search for that location.

      To achieve the second objective, you'll have a <div> with nested <p> tags that will neatly display your retrieved data.

      Whilst your app is running from your terminal window, you can't type anything else in that particular window. Therefore, open up the weather-app directory in a new terminal window if you want to execute other ng commands. Alternatively, you can stop the app from running in the original terminal window by pressing CTRL + C. You can then install the new component, and after that start the app again by typing ng serve --o.

      Execute the following command that will create your Weather Component and automatically import it into your app.module.ts file. Remember that your app.module.ts file holds details about all the components, modules, and providers in your application.

      • ng generate component weather

      You'll see output like this (the exact byte sizes may vary):

      Output

      CREATE src/app/weather/weather.component.css (0 bytes) CREATE src/app/weather/weather.component.html (26 bytes) CREATE src/app/weather/weather.component.spec.ts (635bytes) CREATE src/app/weather/weather.component.ts (273 bytes) UPDATE src/app/app.module.ts (400 bytes) ...

      This output shows that Angular has created the four files necessary for a component:

      • The .css and .html files for your view
      • A .spec.ts file for testing your component
      • A.component.ts file to hold your component's functions

      Angular has also updated the src/app/app.module.ts file to add a reference to the newly created component. You'll always find component files under the src/app/name-of-component directory.

      Now that you have installed your new component, return to your browser to see the app. If you stopped the app running to install the new component, start it again by typing:

      You'll notice that you can still see "Welcome to app!" (the default component) displayed on the page. You can't see your newly created component. In the next section, you'll change this so that whenever you go to localhost:4200, you'll access your newly created weather component instead of Angular's default component.

      Step 4 — Accessing Your Weather Component

      In standard HTML, whenever you want to create a new page, you create a new .html file. For example, if you already had a pre-existing HTML page from which you wanted to navigate to your newly created page, you'd have an href attribute with an anchor tag to point to that new page. For example:

      preexisting.html

      <a href="http://www.digitalocean.com/newpage.html">Go to New Page</a>
      

      In Angular, however, this works slightly differently. You cannot use an href attribute in this way to navigate to a new component. When you want to link through to a component, you need to make use of Angular's Router library and declare a desired URL path within a file that will map directly to a component.

      In Angular, you call this file routes.ts. This holds all the details of your routes (links). For this file to work correctly, you will import the Routes type from the @angular/router library and list your desired links to be of type Routes. This will communicate to Angular that these are a list of routes for navigation in your app.

      Create the file routes.ts in your text editor and save it in the src/app directory. Next, add the following content to the routes.ts file:

      src/app/routes.ts

      import { Routes } from '@angular/router'
      

      Now, declare the URL path and the component in src/app/routes.ts. You want to make your app such that when you go to the homepage (http://localhost:4200), you access your newly created Weather Component. Add these lines to the file, which will map the root URL to the Weather Component you just created:

      src/app/routes.ts

      import { Routes } from '@angular/router'
      import { WeatherComponent } from './weather/weather.component';
      
      export const allAppRoutes: Routes = [
        { path: '', component: WeatherComponent }
      ];
      

      You've imported your WeatherComponent, and then created a variable allAppRoutes that's an array of type Routes. The allAppRoutes array holds route definition objects each containing a URL path and the component to map to. You've specified that any time you go to the root URL (''), it should navigate to the WeatherComponent.

      Your final routes.ts file will look like this:

      src/app/routes.ts

      import { Routes } from "@angular/router";
      import { WeatherComponent } from "./weather/weather.component";
      
      export const allAppRoutes: Routes = [
        { path: '', component: WeatherComponent }
      ];
      

      You now need to add these routes to your main app.module.ts file. You need to pass the array you just created — allAppRoutes — into an Angular module called the RouterModule. The RouterModule will initialize and configure the Router (responsible for carrying out all app navigation) and provide it with its routing data from allAppRoutes. Add the following highlighted content:

      src/app/app.module.ts

      ...
      import {WeatherComponent} from './weather/weather.component';
      import {RouterModule} from '@angular/router';
      import {allAppRoutes} from './routes';
      ...
      @NgModule({
          declarations:[
            ...
          ],
          imports: [
              BrowserModule,
              RouterModule.forRoot(allAppRoutes)
          ]
          ...
      })
      ...
      

      In this file, you've imported the RouterModule and allAppRoutes array of route objects. You've then passed the allAppRoutes array into the RouterModule so that your Router knows where to route your URLs to.

      Lastly, you need to enable routing itself. Open the app.component.ts file. There's a templateUrl property that specifies the HTML for that particular component: ./app.component.html. Open this file, src/app/app.component.html, and you will see that it contains all of the HTML for your localhost:4200 page.

      Remove all of the HTML contained within app.component.html and replace it with:

      src/app/app.component.html

      <router-outlet></router-outlet>
      

      The router-outlet tag activates routing and matches the URL the user types into the browser to the route definition you created earlier in the routes.ts file under the allAppRoutes variable. The router then displays the view in the HTML. In this tutorial, you'll display the weather.component.html code directly after the <router-outlet></router-outlet> tag.

      Now, if you navigate to http://localhost:4200, you will see weather works! appear on your page.

      You've set up routing in your application. Next, you'll create your form and details section that will enable you to search for a location and show its associated details.

      Step 5 — Defining the User Interface

      You'll be using Bootstrap to act as the scaffolding for your application view. Bootstrap is useful for creating ready-made, responsive websites that adapt to any device (mobile, tablet, or desktop). It achieves this by treating every row on a webpage as twelve columns wide. On a webpage, a row is simply a line from one end of the page to the other. This means that every page's content must be contained within that line, and it must equal twelve columns. If it doesn't equal twelve columns, it'll be pushed down to another row. For example, in Bootstrap's grid system, there would be a twelve-column row divided into two sections of six columns, and the next twelve-column row divided into three sections of four columns.

      In the Bootstrap documentation, you can read more about this grid system.

      You'll be splitting your page into two sections of six columns with the left column holding your search form and the right showing the weather details.

      Open src/app/weather/weather.component.html to access your WeatherComponent HTML code. Delete the paragraph that is currently in the file, and then add the following code:

      src/app/weather/weather.component.html

      <div class="container">
        <div class="row">
          <div class="col-md-6"><h3 class="text-center">Search for Weather:</h3></div>
          <div class="col-md-6"><h3 class="text-center">Weather Details:</h3></div>
        </div>
      </div>
      
      

      You created a <div> with class container to hold all your content. You then created a row that you split into two sections of six columns each. The left-hand side will hold your search form and the right, your weather data.

      Next, to build your form, you'll work in the first col-md-6 column. You'll also add a button that will submit what you've typed into your form to APIXU, which will then return the requested weather details. To do this, identify the first col-md-6 class and add the following highlighted content underneath the <h3> tag:

      src/app/weather/weather.component.html

      ...
      <div class="col-md-6">
        <h3 class="text-center">Search for Weather:</h3>
        <form>
          <div class="form-group">
            <input
              class="form-control"
              type="text"
              id="weatherLocation"
              aria-describedby="weatherLocation"
              placeholder="Please input a Location"
            />
           </div>
           <div class="text-center"> 
            <button type="submit" class="btn btn-success btn-md">
              Search for the weather</button>
           </div>
         </form> 
      </div>
      ...
      

      You've added your form and added a form-group class that holds your search bar. You've also created your button to search for the weather. In your browser, your weather app page will look like this:

      Image of weather app page so far

      This looks a little compact, so you can add some CSS in order to style the page with some better spacing. The major advantage of Bootstrap is that it comes with spacing classes that you can add to your HTML without needing to write any extra CSS of your own. If, however, there is any extra CSS you would like to incorporate that Bootstrap's standard classes don't cover, you can write in your own CSS as necessary. For this tutorial, you will use Bootstrap's standard classes.

      For every <h3> tag, you will add the .my-4 Boostrap CSS class. The m sets margin on the element, the y sets both margin-top and margin-bottom on the element, and finally 4 specifies the amount of margin to add. You can find out more details about the different spacing types and sizes here. In your weather.component.html file, add the following highlighted content to replace the current <h3> tags:

      src/app/weather/weather.component.html

      <div class="col-md-6">
        <h3 class="text-center my-4">Search for Weather:</h3>
        <form>
          <div class="form-group">
            <input
              class="form-control"
              type="text"
              id="weatherLocation"
              aria-describedby="weatherLocation"
              placeholder="Please input a Location"
            />
          </div>
          <div class="text-center">
            <button type="submit" class="btn btn-success btn-md">
              Search for the weather
            </button>
          </div>
        </form>
      </div>
      <div class="col-md-6">
        <h3 class="text-center my-4">Weather Details:</h3>
      </div>
      

      Reload the page in your browser and you'll see that you have more spacing.

      Image of spacing applied to weather app

      You've created your form as well as the section where you're going to display the information you receive from the APIXU API. Next, you'll wire up your form to be able to input your location correctly.

      Step 6 — Wiring Up Your Form

      In Angular, there are two ways of creating forms for user input in your application — reactive or template-driven. Although they achieve the same result, each form type handles processing user input data differently.

      With reactive forms, you create a list of the different elements of your form in your .component.ts file. You then connect them to your created HTML form within the respective .component.html file. This is strictly one-way; that is, data flows from your HTML to your .component.ts file, there is no bi-directional flow of data.

      With template-driven forms, you create your form as you would in normal HTML. Then, using directives such as ngModel, you can create either one-way or two-way data bindings from your HTML, back to your data model in your component, and vice-versa.

      There are strengths and weaknesses in each approach, but in general, reactive forms are preferable because of the:

      • Flexibility to create forms of varying complexities.
      • Simplicity to unit test by checking on the state of each form control in the component's .component.ts file.
      • Capability to subscribe to values within a form. A developer can subscribe to the form's value stream allowing them to perform some action on values being typed into the form in real time.

      Despite these strengths, reactive forms can sometimes be more complex to implement. This can lead to developers writing more code than compared to a template-driven form. To see a comprehensive overview of both form types and best use cases, Angular's official guide provides a good starting point. For this tutorial, you'll be using reactive forms.

      To use a reactive form, open the file app.module.ts. Next, import the ReactiveFormsModule by declaring the import toward the top of the file.

      src/app/app.module.ts

      ...
      import { ReactiveFormsModule } from '@angular/forms';
      @NgModule({
          ...
      })
      ...
      

      Finally, add the ReactiveFormsModule to your list of imports.

      src/app/app.module.ts

      ...
      @NgModule({
          ...
          imports: [
              BrowserModule,
              RouterModule.forRoot(allAppRoutes),
              ReactiveFormsModule
          ]
          ...
      })
      ...
      

      Following these code additions, your app.module.ts will look like this:

      src/app/app.module.ts

      import { BrowserModule } from "@angular/platform-browser";
      import { NgModule } from "@angular/core";
      
      import { AppComponent } from "./app.component";
      import { WeatherComponent } from "./weather/weather.component";
      import { RouterModule } from "@angular/router";
      import { allAppRoutes } from "./routes";
      import { ReactiveFormsModule } from "@angular/forms";
      
      @NgModule({
        declarations: [AppComponent, WeatherComponent],
        imports: [
          BrowserModule,
          RouterModule.forRoot(allAppRoutes),
          ReactiveFormsModule
        ],
        providers: [],
        bootstrap: [AppComponent]
      })
      export class AppModule {}
      

      Once you've added both of these lines, open the weather.component.ts file and import the FormBuilder and FormGroup classes.

      src/app/weather/weather.component.ts

      import { Component, OnInit } from '@angular/core';
      import { FormBuilder, FormGroup } from '@angular/forms';
      

      Now create a variable in your weather.component.ts file that will reference your FormGroup:

      weather.component.ts

      export class WeatherComponent implements OnInit {
         public weatherSearchForm: FormGroup;
         constructor() { }
      ...
      

      Every time you want to perform an action on your form, you'll reference it via the weatherSearchForm variable. You'll now add the FormBuilder import into your constructor so that you can use it in your component.

      weather.component.ts

      ...
      public weatherSearchForm: FormGroup;
      constructor(private formBuilder: FormBuilder) {}
      ...
      

      By adding the formBuilder to the constructor, it creates an instance of the FormBuilder class, allowing you to use it within your component.

      You are now ready to create your FormGroup and its respective values in the weather.component.ts file. If you have several input options in your form, it's best practice to enclose it within a FormGroup. In this tutorial, you will only have one (your location input), but you will use the FormGroup anyway for practice.

      It's important that your form is ready for use when you navigate to your component. Because you're using a reactive form, you must create the tree of elements within the form first before you bind it to the HTML. To achieve this, you need to ensure that you create your form elements in the ngOnInit hook inside your WeatherComponent. The ngOnInit method runs once at the initialization of a component, executing any logic that you specify needs to run before the component is ready to use.

      You therefore have to create your form before you can complete the binding to HTML process.

      In your WeatherComponent, you'll initialize the form within the ngOnInit hook:

      src/app/weather/weather.component.ts

      ...
      constructor(private formBuilder: FormBuilder) {}
      ngOnInit() {
          this.weatherSearchForm = this.formBuilder.group({
            location: ['']
          });
        }
      

      You have created the first part of the form according to reactive form style: defining your form components in the weather.component.ts file. You've created a group of your form's composite elements (at the moment, you have one element, location). The [''] array allows you to specify some extra options for your form inputs such as: pre-populating it with some data and using validators to validate your input. You have no need of any of these for this tutorial, so you can just leave it blank. You can find out more about what you can pass into an element property here.

      You have two more things to do before your form is complete. First open up your weather.component.html file. You need to assign the form a property [formGroup]. This property will be equal to the variable you just declared in your weather.component.ts file: weatherSearchForm. Second, you have to bind your location element (declared in your weather.component.ts file) to your HTML. In weather.component.html, add the following highlighted content:

      src/app/weather/weather.component.html

      ...
      <form
        [formGroup]="weatherSearchForm" >
        <div class="form-group">
          <input
            class="form-control"
            type="text"
            id="weatherLocation"
            aria-describedby="weatherLocation"
            placeholder="Please input a Location"
          />formControlName="location" />
        </div>
        <div class="text-center">
          <button type="submit" class="btn btn-success btn-md">
            Search for the weather
          </button>
        </div>
      </form>
      ...
      

      You've added the [formGroup] property, binding your form to HTML. You've also added the formControlName property that declares that this particular input element is bound to the location element in your weather.component.ts file.

      Save your file and return to your browser, you'll see that your app looks exactly the same. This means that your form is correctly wired up. If you see any errors at this stage, then please go back through the previous steps to ensure that everything is correct in your files.

      Next, you'll wire up your button to be able to accept input data into your form.

      Step 7 — Connecting Your Button

      In this step you're going to connect your search button to your form in order to be able to accept the user's input data. You're also going to create the scaffolding for the method that will eventually send the user's input data to the APIXU weather API.

      If you take a look back at your code in weather.component.html, you can see that your button has a type submit:

      src/app/weather/weather.component.html

      <form>
      ...
      <div class="text-center">
          <button type="submit" class="btn btn-success btn-md">Search for the weather</button>
      </div>
      </form>
      

      This is a standard HTML value that will submit your form values to some function to take action on.

      In Angular, you specify that function in the (ngSubmit) event. When you click your button in your form, as long as it has a type of submit, it will trigger the (ngSubmit) event, which will subsequently call whatever method you have assigned to it. In this case, you want to be able to get the location that your user has typed in and send it to the APIXU API.

      You're going to first create a method to handle this. In your weather.component.ts, create a method sendToAPIXU() that will take one argument: the value(s) you've typed into your form. Add the following highlighted content to the file:

      src/app/weather/weather.component.ts

      ...
      ngOnInit() {
          this.weatherSearchForm = this.formBuilder.group({
            location: [""]
          });
        }
      
      sendToAPIXU(formValues) {
      
      }
      ...
      

      Next, add the ngSubmit event to your HTML and pass the values of your submitted form into the sendToAPIXU() method:

      weather.component.html

      ...
      <form [formGroup]="weatherSearchForm" (ngSubmit)="sendToAPIXU(weatherSearchForm.value)">
        ...
      </form>
      ...
      

      You've added the ngSubmit event to your form, connected your method you want to run when you submit your form, and passed in the values of your weatherSearchForm as an argument to your handler method (weatherSearchForm.value). You can now test this works by using console.log to print out your formValues, in your sendToAPIXU() method, add the following highlighted content to weather.component.ts:

      weather.component.ts

      ...
      sendToAPIXU(formValues){
          console.log(formValues);
      }
      

      Go to your browser and open your console by right clicking anywhere on your website page, and then click on Inspect Element. There will be a tab on the window that pops up called Console. Type London into your form. When you click on the Search for Weather button, you'll see an object with your location enclosed.

      Output from console after updating the sendToAPIXU method

      Your output from the console is a JSON object {location: "London"}. If you wanted to access your location value, you can do this by accessing formValues.location. Similarly, if you had any other inputs inside your form, you would swap .location for any other element names you had.

      Note:
      All values of a reactive form are stored in an object — where the key is the name of the value you passed into the formBuilder.group({}).

      The button is now wired up and can receive input correctly. Next, you'll make the sendToAPIXU() method make an HTTP request to the APIXU API.

      Step 8 — Calling the APIXU API

      The APIXU API accepts location information, searches the current weather details for that location, and returns them back to the client. You'll now modify your app so that it sends location data to the API, obtains the response, and then displays the results on your page.

      In order to make HTTP requests in Angular, you have to import the HttpClientModule. Open your src/app/app.module.ts and add the following highlighted lines:

      src/app/app.module.ts

      ...
      import { ReactiveFormsModule } from '@angular/forms';
      import { HttpClientModule } from '@angular/common/http';
      @NgModule({
          ...
          imports: [
              BrowserModule,
              RouterModule.forRoot(allAppRoutes),
              ReactiveFormsModule,
              HttpClientModule
          ]
          ...
      })
      ...
      

      Next, you need to write the code to make the HTTP call to the APIXU API. It's best practice to create an Angular service to make HTTP requests. Separation of concerns is key in any app that you build. A service allows you to move all of those HTTP requests your app makes into one file that you can then call inside any .component.ts file you create. You could "legally" write in those HTTP requests in the specific .component.ts file, but this isn't best practice. You may, for instance, find that some of your requests are complex and require you to perform some post-processing actions after receiving your data. Several different components in your app might use some of your HTTP requests, and you don't want to write the same method multiple times.

      From a new terminal window or by stopping the server in your current terminal session, execute the following command to create a service called apixu:

      You'll see output resembling the following:

      Output

      create src/app/apixu.service.spec.ts (328 bytes) create src/app/apixu.service.ts (134 bytes) ...

      The command created the service file (apixu.service.ts) and a test file (apixu.service.spec.ts).

      You now need to add this service as a provider into your app.module.ts file. This makes it available to use inside your app. Open this file, and first import the ApixuService:

      src/app/app.module.ts

      ...
      import { HttpClientModule } "@angular/common/http";
      import { ApixuService } from "./apixu.service";
      ...
      

      Next add the newly imported ApixuService as a provider into the providers block:

      src/app/app.module.ts file

      ...
      @NgModule({
          ...
          providers: [ApixuService],
          ...
      })
      ...
      

      In Angular, if you want to use a service that you have created, you need to specify that service as a provider within your module.ts file. In this case, you've specified it as a provider within your entire application in app.module.ts.

      Finally, open up the src/app/apixu.service.ts file. You'll see the boilerplate code of what you need to create a service: first the import of the Injectable interface from Angular; then the fact that the service should be with the providedIn root injector (for the entire application); and then the decorating (this effectively means specifying) of your service as @Injectable.

      src/app/apixu.service.ts

      import { Injectable } from '@angular/core';
      
      @Injectable({
        providedIn: 'root'
      })
      export class ApixuService {
      
        constructor() { }
      }
      

      The decorating of the service as @Injectable allows you to inject this service within the constructor in weather.component.ts so that you can use it inside your component.

      If you stopped your application, restart it by running:

      As aforementioned, your service needs to make HTTP requests to the APIXU API and import the HttpClientModule in the app.module.ts file to make HTTP requests throughout the application. You additionally need to import the HttpClient library into the apixu.service.ts file to make HTTP requests to the APIXU API from the apixu.service.ts file itself. Open the apixu.service.ts file, and add the following highlighted content:

      src/app/apixu.service.ts

      ...
      import { HttpClient } from '@angular/common/http';
      ...
      

      Now you need to write a method, getWeather(), that takes in one paramater: location. This method will make an API request to APIXU and return the retrieved location data.

      For this, you'll need the provided API key when you signed up for the APIXU API. If you log in to APIXU, you'll come to the dashboard:

      APIXU Dashboard

      You will see your key, and below that, links to the API URL with your key already pre-filled for both the Current Weather and Forecast Weather. Copy the HTTPS link for the Current Weather details, it will be something like:

      https://api.apixu.com/v1/current.json?key=YOUR_API_KEY&q=Paris

      This URL will give you current weather details for Paris. You want to be able to to pass in the location from your form into the &q= parameter instead. Therefore, remove Paris from the URL as you add it to your apixu.service.ts file:

      src/app/apixu.service.ts

      ...
      export class ApixuService {
      
        constructor(private http: HttpClient) {}
      
        getWeather(location){
            return this.http.get(
                'https://api.apixu.com/v1/current.json?key=YOUR_API_KEY&q=' + location
            );
        }
      }
      

      Note: You've used the API key directly within the code. In a production situation, you should store this securely server-side, and retrieve this key in a secure manner and use it within your application. You can either store it securely server-side, or use a key management application such as Hashicorp Vault or Azure Key Vault, to name a few.

      You've now imported and injected HttpClient into the constructor so that you can use it. You've also created a method getWeather() that takes a location parameter and makes a GET request to your provided URL. You left the &q= parameter blank, as you're going to provide this location directly from location parameter in the method. Lastly, you've returned the data back to whoever called the method.

      Your service is now complete. You need to import your service into your WeatherComponent, inject it into your constructor to use it, and then update your sendToAPIXU() method to send your location to your newly created service. Open the weather.component.ts file to complete these tasks by adding the highlighted content:

      src/app/weather.component.ts

      ...
      import { FormBuilder, FormGroup } from "@angular/forms";
      import { ApixuService } from "../apixu.service";
      ...
      constructor(
          private formBuilder: FormBuilder,
          private apixuService: ApixuService
        ) {}
      ...
      ngOnInit(){...}
      sendToAPIXU(formValues){
          this.apixuService
            .getWeather(formValues.location)
            .subscribe(data => console.log(data));
      }
      

      You've removed the former console.log statement in your sendToAPIXU() method and updated it with this content. You're now passing in your location from your form to the sendToAPIXU() method you created earlier. You've then passed that data to the getWeather() method of the ApixuService that has subsequently made an HTTP request to the API with that location. You've then subscribed to the response you got back and, in this example, logged that data to the console. You always have to call the subscribe method on an HTTP request as the request will not begin until you have a way of reading the Observable response you get back. Observables are a way of sending messages between publishers and subscribers, allowing you to pass any kind of data back and forth. You will not be able to receive data from an observable until a subscriber has subscribed to it, because it won't execute before that point.

      Open the console in your browser again again. Now, type in London, UK and click Search for Weather. If you click on the tab arrows, you'll see a list of the weather details in the console.

      Console output from looking for current weather in London, UK

      The output shows JSON objects containing all of the weather information needed. You have two objects returned: a current object and a location object. The former gives the desired weather details and the latter details about your location.

      You've now got your weather data successfully showing in the console. To finish this tutorial, you'll display these weather details in your HTML.

      Step 9 — Displaying Your Weather Data in Your App

      Displaying the results in the console is a good initial step to check that everything is working. However, you want to eventually show the weather data in HTML for your users. To do this, you'll create a variable to hold your returned weather data, and then display that using interpolation in your HTML.

      Interpolation allows you to display data in your views. To do this, it requires you to bind a property via the {{ }} style, to show that property in your HTML.

      Open up the weather.component.ts file and create a variable called weatherData to which you'll assign the retrieved JSON data from the API. Additionally, remove the code that was previously in the .subscribe() brackets and replace it with the following highlighted code:

      src/app/weather/weather.component.ts

      ...
      export class WeatherComponent implements OnInit {
      public weatherSearchForm: FormGroup;
      public weatherData: any;
      ...
      sendToAPIXU(formValues){
          this.apixuService
          .getWeather(formValues.location)
          .subscribe(data => this.weatherData = data)
            console.log(this.weatherData);
          }
      }
      

      You've created the variable weatherData and declared that it can hold data of any type. You've then assigned the data you receive back from your API call to that variable. Finally, you've added a console.log() statement to double check that weatherData holds all of your retrieved information.

      Your weather.component.ts file should be looking like this at this stage:

      src/app/weather/weather.component.ts

      import { Component, OnInit } from "@angular/core";
      import { FormBuilder, FormGroup } from "@angular/forms";
      import { ApixuService } from "../apixu.service";
      
      @Component({
        selector: "app-weather",
        templateUrl: "./weather.component.html",
        styleUrls: ["./weather.component.css"]
      })
      export class WeatherComponent implements OnInit {
        public weatherSearchForm: FormGroup;
        public weatherData: any;
      
        constructor(
          private formBuilder: FormBuilder,
          private apixuService: ApixuService
        ) {}
      
        ngOnInit() {
          this.weatherSearchForm = this.formBuilder.group({
            location: [""]
          });
        }
      
        sendToAPIXU(formValues) {
          this.apixuService.getWeather(formValues.location).subscribe(data => {
            this.weatherData = data;
            console.log(this.weatherData);
          });
        }
      }
      

      If you go back and search for London, UK again, you'll see your object printed out to the console as normal. Now, you want to show this data in your HTML. If you examine the current object from the retrieved weather data in the console, you'll see values such as condition, feelslike_c, feelslike_f, temp_c, temp_f, and so on You're going to make use of all five of these properties.

      Open your weather.component.html file again and add in the subtitles to the data you want to display. You'll be adding these <p> tags within the second col-md-6:

      src/app/weather/weather.component.html

      ...
      <div class="col-md-6">
        <h3 class="text-center my-4">Weather Details:</h3>
        <p class="text-center">Current weather conditions:</p>
        <p class="text-center">Temperature in Degrees Celsius:</p>
        <p class="text-center">Temperature in Degrees Farenheit:</p>
        <p class="text-center">Feels like in Degrees Celsius:</p>
        <p class="text-center">Feels like in Degrees Farenheit:</p>
        <p class="text-center">Location Searched:</p>
      </div>
      

      Next, you'll add the data you have received from your JSON object to your HTML:

      weather.component.html

      ...
      <h3 class="text-center my-4 ">Weather Details:</h3>
      <p class="text-center">
        Current weather conditions: {{this.weatherData?.current.condition.text}}
      </p>
      <p class="text-center">
        Temperature in Degrees Celsius: {{this.weatherData?.current.temp_c}}
      </p>
      <p class="text-center">
        Temperature in Degrees Farenheit: {{this.weatherData?.current.temp_f}}
      </p>
      <p class="text-center">
        Feels like in Degrees Celsius: {{this.weatherData?.current.feelslike_c}}
      </p>
      <p class="text-center">
        Feels like in Degrees Farenheit:
        {{this.weatherData?.current.feelslike_f}}
      </p>
      <p class="text-center">
        Location Searched: {{this.weatherData?.location.name}},
        {{this.weatherData?.location.country}}
      </p>
      

      You have used an operator ? as you retrieved data from your weatherData variable within your HTML. This operator is called an Elvis Operator.

      Because you're making an HTTP call, you're making an asynchronous request. You'll get that data back at some point, but it will not be an immediate response. Angular, however, will still continue to fill out your HTML with the data you specified from the weatherData variable. If you haven't received data back by the time that Angular begins to populate your paragraphs, there will be an error stating that Angular can't find that data. For example, .current or .location would be showing as undefined.

      The Elvis Operator is a safe navigator and prevents this from happening. It tells Angular to wait and check if weatherData is first defined, before going ahead and showing that data in the HTML. Once weatherData has all of its information, Angular will then update your bindings and show your data as normal.

      You final weather.component.ts file will look like the following:

      weather.component.html

      <div class="container">
        <div class="row">
          <div class="col-md-6">
            <h3 class="text-center my-4">Search for Weather:</h3>
            <form
              [formGroup]="weatherSearchForm"
              (ngSubmit)="sendToAPIXU(weatherSearchForm.value)"
            >
              <div class="form-group">
                <input
                  class="form-control"
                  type="text"
                  id="weatherLocation"
                  aria-describedby="weatherLocation"
                  placeholder="Please input a Location"
                  formControlName="location"
                />
              </div>
              <div class="text-center">
                <button type="submit" class="btn btn-success btn-md">
                  Search for the weather
                </button>
              </div>
            </form>
          </div>
          <div class="col-md-6">
            <h3 class="text-center my-4">Weather Details:</h3>
            <p class="text-center">
              Current weather conditions: {{ this.weatherData?.current.condition.text
              }}.
            </p>
            <p class="text-center">
              Temperature in Degrees Celsius: {{ this.weatherData?.current.temp_c }}
            </p>
            <p class="text-center">
              Temperature in Degrees Farenheit: {{ this.weatherData?.current.temp_f }}
            </p>
            <p class="text-center">
              Feels like in Degrees Celsius: {{ this.weatherData?.current.feelslike_c
              }}
            </p>
            <p class="text-center">
              Feels like in Degrees Farenheit: {{
              this.weatherData?.current.feelslike_f }}
            </p>
            <p class="text-center">
              Location Searched: {{ this.weatherData?.location.name }}, {{
              this.weatherData?.location.country }}.
            </p>
          </div>
        </div>
      </div>
      

      You've followed the pattern of the returned JSON weather object in order to output your desired data. Save your file, go back to your browser, and type London, UK, you'll see your weather data appear on the right-hand side.

      Finished app showing weather data from London, UK

      Try it with different locations, like: San Francisco, US, Dakar, Senegal, and Honololu, Hawaii. You'll see the respective weather data appear for all those locations.

      Conclusion

      You have created a weather app using Angular, Bootstrap, and the APIXU API. You have set up an Angular project from scratch, following Angular best practices while ensuring your application is well designed and set up appropriately.

      Angular is an advanced framework allowing you to create anything from small web applications to large, complex ones with ease. Angular, as with any frameworks, does have a learning curve, but small projects like this one can help you to quickly learn and start using it productively.

      Another feature to consider adding to your application is handling errors from your HTTP requests; for instance, if you were to type in an invalid location. Another enhancement would be displaying different images if the temperature is between certain thresholds. You can also create different applications with Angular using other APIs.

      You may also want to use NgBootstrap, which is a special type of Bootstrap built for Angular. This allows you to use all the standard Bootstrap JavaScript widgets as well as some special ones not included in the standard installation specifically adapted for Angular.

      The full code for this tutorial is available on GitHub.



      Source link