One place for hosting & domains

      Bootstrap

      How To Bootstrap a New Laravel Application with Docker Compose



      Part of the Series:
      How To Build a Links Landing Page in PHP with Laravel and Docker Compose

      Laravel is an open-source PHP framework that provides a set of tools and resources to build modern PHP applications. In this project-based tutorial series, you’ll build a Links Landing Page application with the Laravel framework, using a containerized PHP development environment managed by Docker Compose.

      At the end, you’ll have a one-page website built with Laravel and managed via Artisan commands where you can share relevant links to an audience on social channels and presentations.

      To get started, you’ll need to create a containerized environment able to execute PHP and Composer, the PHP dependency management tool. Then, you’ll be able to bootstrap the new Laravel application from scratch, without the need to have a local PHP environment installed on your local machine or development server.

      In this guide, we’ll provide streamlined instructions on how to set this environment up based on our tutorial on How To Install Laravel with Docker Compose on Ubuntu 20.04. Please refer to that tutorial for more detailed instructions on each of the options used within the Docker Compose file that will be provided in this guide.

      Create a new directory for your application in your home folder:

      • mkdir ~/landing-laravel
      • cd ~/landing-laravel

      Next, you’ll create the docker-compose.yml file that will define the containerized environment. In this file, you’ll set up a service named app, which will be based on a custom Docker image built with a Dockerfile you’ll set up later on.

      The build arguments user and uid, both defined in the docker-compose.yml file and used in the Dockerfile at build time, should be changed to reflect your own username and uid on your local machine or development server. To find out your current user’s uid, type:

      Output

      1000

      The user and uid variables will be available at build time and will be used in the Dockerfile to create a new user in the app service with the same username and uid as your current system user on your local machine or development server. This will avoid permission and ownership issues when working with application files both from the container as well as from the host that executes Docker.

      Create a new docker-compose.yml file using your text editor of choice. Here, we’re using nano:

      Copy the following content to this file, and don’t forget to replace the highlighted values with appropriate values depending on your own username and uid on the system that runs Docker:

      ~/landing-laravel/docker-compose.yml

      version: "3.7"
      services:
        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: landing-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - landing
      
      networks:
        landing:
          driver: bridge
      

      Save and close the file when you are done. If you are using nano, you can do that by pressing CTRL+X, then Y and ENTER to confirm.

      Next, you’ll set up the Dockerfile that is referenced in the docker-compose.yml file, which will set up a custom image for the app service:

      This Dockerfile extends from the default php:7.4-fpm Docker image. It uses the user and uid variables to create a new user able to execute Artisan and Composer commands. It also installs a few PHP dependencies that are required by Laravel, and the Composer executable.

      Copy the following content to your Dockerfile:

      ~/my-todo-list/Dockerfile

      FROM php:7.4-fpm
      
      # Arguments defined in docker-compose.yml
      ARG user
      ARG uid
      
      # Install system dependencies
      RUN apt-get update && apt-get install -y 
          git 
          curl 
          libpng-dev 
          libonig-dev 
          libxml2-dev 
          zip 
          unzip
      
      # Clear cache
      RUN apt-get clean && rm -rf /var/lib/apt/lists/*
      
      # Install PHP extensions
      RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd
      
      # Get latest Composer
      COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
      
      # Create system user to run Composer and Artisan Commands
      RUN useradd -G www-data,root -u $uid -d /home/$user $user
      RUN mkdir -p /home/$user/.composer && 
          chown -R $user:$user /home/$user
      
      # Set working directory
      WORKDIR /var/www
      
      USER $user
      

      Save and close the file when you’re done. Next, you can bring your environment up with:

      This command will execute Docker Compose in detached mode, which means it will run in the background. The first time you bring an environment up with a custom image, Docker Compose will automatically build the image for you before creating the required containers. This might take a few moments to finish. You’ll see output similar to this:

      Output

      Creating network "landing-laravel_landing" with driver "bridge" Building app Step 1/11 : FROM php:7.4-fpm ---> fa37bd6db22a ... Step 10/11 : WORKDIR /var/www ---> Using cache ---> 769afd5d44d8 Step 11/11 : USER $user ---> Using cache ---> 841eb5852b69 Successfully built 841eb5852b69 Successfully tagged landing-app:latest WARNING: Image for service app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`. Creating landing-laravel_app_1 ... done

      You can verify that your environment is up and running with:

      Output

      Name Command State Ports ------------------------------------------------------------------------ landing-laravel_app_1 docker-php-entrypoint php-fpm Up 9000/tcp

      Once the app service is up, you can run Composer, the PHP dependency management tool, to bootstrap a new Laravel application. In order to do that, you’ll use docker compose exec to run commands on the app service, where PHP is installed.

      The following command will use Docker Compose to execute composer create-project, which will bootstrap a fresh installation of Laravel based on the laravel/laravel package:

      • docker-compose exec app composer create-project laravel/laravel --prefer-dist application
      Creating a "laravel/laravel" project at "./application"
      Installing laravel/laravel (v8.4.0)
        - Downloading laravel/laravel (v8.4.0)
        - Installing laravel/laravel (v8.4.0): Extracting archive
      Created project in /var/www/application
      > @php -r "file_exists('.env') || copy('.env.example', '.env');"
      Loading composer repositories with package information
      Updating dependencies
      Lock file operations: 104 installs, 0 updates, 0 removals
      …
      Package manifest generated successfully.
      71 packages you are using are looking for funding.
      Use the `composer fund` command to find out more!
      > @php artisan key:generate --ansi
      Application key set successfully.
      

      This installation creates a new .env file based on the default .env.example file that comes with Laravel. The .env file contains database credentials and other sensitive application settings, and should be unique per environment where the app runs. You’ll come back to edit this file after you finish setting up the development environment.

      Next, copy the application files to the same directory as the docker-compose.yml file, so that you can share Laravel’s environment variables file with Docker Compose. Then, you can remove the application directory created by Composer:

      cp -rT application .
      rm -rfv application
      

      Your application is now bootstrapped, but you’ll need to include a couple services in the Docker Compose file in order to be able to access the app from a browser. An nginx service will serve the application using the Nginx web server, and a db service will host the application’s MySQL database.

      First, bring your environment down with:

      Output

      Stopping landing-laravel_app_1 ... done Removing landing-laravel_app_1 ... done Removing network landing-laravel_landing

      This will remove all containers and networks associated with this environment. Before editing your docker-compose.yml file to add the new services, create a new directory to share configuration files with containers. You’ll need this to properly set up Nginx to handle the Laravel PHP application.

      • mkdir -p docker-compose/nginx

      Next, create a new landing-laravel.conf file containing a custom Nginx server block. Later on, you’ll set up a volume to share this file within the nginx service container.

      Open a new Nginx configuration file with:

      • nano docker-compose/nginx/landing-laravel.conf

      The following server block configures Nginx to serve a Laravel application using an external service (app) to handle PHP code. Copy this content to your own Nginx configuration file:

      docker-compose/nginx/landing-laravel.conf

      server {
          listen 80;
          index index.php index.html;
          error_log  /var/log/nginx/error.log;
          access_log /var/log/nginx/access.log;
          root /var/www/public;
          location ~ .php$ {
              try_files $uri =404;
              fastcgi_split_path_info ^(.+.php)(/.+)$;
              fastcgi_pass app:9000;
              fastcgi_index index.php;
              include fastcgi_params;
              fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
              fastcgi_param PATH_INFO $fastcgi_path_info;
          }
          location / {
              try_files $uri $uri/ /index.php?$query_string;
              gzip_static on;
          }
      }
      

      Save and close the file when you’re done.

      Next, open your docker-compose.yml file:

      Include the following configuration for the nginx service, at the same level as the previously configured app service. This will create a new service based on the nginx:alpine image, and all requests on port 8000 of the host where Docker is running will be redirected to port 80 in the service container. In addition to the application files, you’ll also share a volume containing Nginx’s configuration file for a Laravel application:

        nginx:
          image: nginx:alpine
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d/
          networks:
            - landing
      
      

      Then, include the following configuration block for the db service. This will create a service based on the default MySQL 8 image, and pull in the values defined in Laravel’s environment file to set up database access:

        db:
          image: mysql:8
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
          networks:
            - landing
      

      This is how your updated docker-compose.yml file should look like once you’re finished:

      ~/landing-laravel/docker-compose.yml

      version: "3.7"
      services:
        app:
          build:
            args:
              user: sammy
              uid: 1000
            context: ./
            dockerfile: Dockerfile
          image: landing-app
          restart: unless-stopped
          working_dir: /var/www/
          volumes:
            - ./:/var/www
          networks:
            - landing
      
        nginx:
          image: nginx:alpine
          restart: unless-stopped
          ports:
            - 8000:80
          volumes:
            - ./:/var/www
            - ./docker-compose/nginx:/etc/nginx/conf.d/
          networks:
            - landing
        db:
          image: mysql:8
          restart: unless-stopped
          environment:
            MYSQL_DATABASE: ${DB_DATABASE}
            MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
            MYSQL_PASSWORD: ${DB_PASSWORD}
            MYSQL_USER: ${DB_USERNAME}
          networks:
            - landing
      
      networks:
        landing:
          driver: bridge
      
      

      Note: for more detailed information about containerizing Laravel environments, including explanations about shared volumes and networks, please refer to our full guide on How To Install Laravel with Docker Compose on Ubuntu 20.04.

      Save and close the file when you’re done editing. Lastly, update your Laravel dot env file (.env) to point the MySQL database host configuration to the host where the MySQL service will be running, called db:

      The .env file that is automatically generated by Composer upon installation comes with some default values that you might want to change, such as the APP_NAME and the APP_URL. The database DB_HOST variable must be changed to point to the service where MySQL will be running, and you can reference it by its service name, as defined in the docker-compose.yml file. In this example, we’ve used db as name for the database service, so this will be available in the containerized network as a host named db.

      Change your .env accordingly, using the following example as base. The highlighted values were updated here to reflect the state of the application under development:

      ~/landing-laravel/.env

      APP_NAME=LandingLaravel
      APP_ENV=local
      APP_KEY=base64:ffYPNP8kPeQDf8gE/qh3kWjk59p6gFY66kCKhhKUa2w=
      APP_DEBUG=true
      APP_URL=http://localhost:8000
      
      LOG_CHANNEL=stack
      LOG_LEVEL=debug
      
      DB_CONNECTION=mysql
      DB_HOST=db
      DB_PORT=3306
      DB_DATABASE=landing-db
      DB_USERNAME=landing-user
      DB_PASSWORD=dev-password
      
      ...
      

      You don’t need to change any other sections of this file, but feel free to tweak to your specific use case.

      Save and close the file when you’re done editing its contents.

      You can now bring the updated environment up with:

      Output

      Creating network "landing-laravel_landing" with driver "bridge" Creating landing-laravel_app_1 ... done Creating landing-laravel_db_1 ... done Creating landing-laravel_nginx_1 ... done

      With the full environment up, you can now point your browser to localhost or your remote server’s IP address, on port 8000:

      http://localhost:8000
      

      If everything works as expected, you’ll see a page like this:

      Laravel Landing Links - basic app

      In the next part of this series, you’ll create a database migration to set up a links table.



      Source link

      How To Add Bootstrap to a Ruby on Rails Application


      Introduction

      If you are developing a Ruby on Rails application, you may be interested in adding styles to your project to facilitate user engagement. One way to do this is by adding Bootstrap, an HTML, CSS, and JavaScript framework designed to simplify the process of making web projects responsive and mobile ready. By implementing Bootstrap in a Rails project, you can integrate its layout conventions and components into your application to make user interactions with your site more engaging.

      In this tutorial, you will add Bootstrap to an existing Rails project that uses the webpack bundler to serve its JavaScript and CSS assets. The goal will be to create a visually appealing site that users can interact with to share information about sharks:

      Application Landing Page

      Prerequisites

      To follow this tutorial, you will need:

      Step 1 — Cloning the Project and Installing Dependencies

      Our first step will be to clone the rails-stimulus repository from the DigitalOcean Community GitHub account. This repository includes the code from the setup described in How To Add Stimulus to a Ruby on Rails Application, which described how to add Stimulus.js to an existing Rails 5 project.

      Clone the repository into a directory called rails-bootstrap:

      • git clone https://github.com/do-community/rails-stimulus.git rails-bootstrap

      Navigate to the rails-bootstrap directory:

      In order to work with the project code, you will first need to install the project’s dependencies, which are listed in its Gemfile. Use the following command to install the required gems:

      Next, you will install your Yarn dependencies. Because this Rails 5 project has been modified to serve assets with webpack, its JavaScript dependencies are now managed by Yarn. This means that it’s necessary to install and verify the dependencies listed in the project’s package.json file.

      Run the following command to install these dependencies:

      • yarn install --check-files

      The --check-files flag checks to make sure that any files already installed in the node_modules directory have not been removed.

      Next, run your database migrations:

      Once your migrations have finished, you can test the application to ensure that it is working as expected. Start your server with the following command if you are working locally:

      If you are working on a development server, you can start the application with:

      • rails s --binding=your_server_ip

      Navigate to localhost:3000 or http://your_server_ip:3000. You will see the following landing page:

      Application Landing Page

      To create a new shark, click on the New Shark link at the bottom of the page, which will take you to the sharks/new route. You will be prompted for a username (sammy) and password (shark), thanks to the project’s authentication settings. The new view looks like this:

      Create New Shark

      To verify that the application is working, we can add some demo information to it. Input “Great White” into the Name field and “Scary” into the Facts field:

      Add Great White Shark

      Click on the Create Shark button to create the shark:

      Show Shark

      You have now installed the necessary dependencies for your project and tested its functionality. Next, you can make a few changes to the Rails application so that users encounter a main landing page before navigating to the shark information application itself.

      Step 2 — Adding a Main Landing Page and Controller

      The current application sets the root view to the main shark information page, the index view for the sharks controller. While this works to get users to the main application, it may be less desirable if we decide to develop the application in the future and add other capabilities and features. We can reorganize the application to have the root view set to a home controller, which will include an index view. From there, we can link out to other parts of the application.

      To create the home controller, you can use the rails generate command with the controller generator. In this case, we will specify that we want an index view for our main landing page:

      • rails generate controller home index

      With the controller created, you’ll need to modify the root view in the project’s config/routes.rb file — the file that specifies the application’s route declarations — since the root view is currently set to the sharks index view.

      Open the file:

      Find the following line:

      ~/rails-bootstrap/config/routes.rb

      . . . 
      root 'sharks#index'
      . . .
      

      Change it to the following:

      ~/rails-bootstrap/config/routes.rb

      . . . 
      root 'home#index'
      . . .
      

      This will set the home controller’s index view as the root of the application, making it possible to branch off to other parts of the application from there.

      Save and close the file when you are finished editing.

      With these changes in place, you are ready to move on to adding Bootstrap to the application.

      Step 3 — Installing Bootstrap and Adding Custom Styles

      In this step, you will add Bootstrap to your project, along with the tool libraries that it requires to function properly. This will involve importing libraries and plugins into the application’s webpack entry point and environment files. It will also involve creating a custom style sheet in your application’s app/javascript directory, the directory where the project’s JavaScript assets live.

      First, use yarn to install Bootstrap and its required dependencies:

      • yarn add bootstrap jquery popper.js

      Many of Bootstrap’s components require JQuery and Popper.js, along with Bootstrap’s own custom plugins, so this command will ensure that you have the libraries you need.

      Next, open your main webpack configuration file, config/webpack/environment.js with nano or your favorite editor:

      • nano config/webpack/environment.js

      Inside the file, add the webpack library, along with a ProvidePlugin that tells Bootstrap how to interpret JQuery and Popper variables.

      Add the following code to the file:

      ~/rails-bootstrap/config/webpack/environment.js

      const { environment } = require('@rails/webpacker')
      const webpack = require("webpack") 
      
      environment.plugins.append("Provide", new webpack.ProvidePlugin({ 
        $: 'jquery',
        jQuery: 'jquery',
        Popper: ['popper.js', 'default']
      }))  
      
      module.exports = environment
      

      The ProvidePlugin helps us avoid the multiple import or require statements we would normally use when working with JQuery or Popper modules. With this plugin in place, webpack will automatically load the correct modules and point the named variables to each module’s loaded exports.

      Save and close the file when you are finished editing.

      Next, open your main webpack entry point file, app/javascript/packs/application.js:

      • nano app/javascript/packs/application.js

      Inside the file, add the following import statements to import Bootstrap and the custom scss styles file that you will create next:

      ~/rails-bootstrap/app/javascript/packs/application.js

      . . . 
      import { Application } from "stimulus"
      import { definitionsFromContext } from "stimulus/webpack-helpers"
      
      import "bootstrap"
      import "../stylesheets/application"
      . . . 
      

      Save and close the file when you are finished editing.

      Next, create a stylesheets directory for your application style sheet:

      • mkdir app/javascript/stylesheets

      Open the custom styles file:

      • nano app/javascript/stylesheets/application.scss

      This is an scss file, which uses Sass instead of CSS. Sass, or Syntactically Awesome Style Sheets, is a CSS extension language that lets developers integrate programming logic and conventions like shared variables into styling rules.

      In the file, add the following statements to import the custom Bootstrap scss styles and Google fonts for the project:

      ~/rails-bootstrap/app/javascript/stylesheets/application.scss

      @import "~bootstrap/scss/bootstrap";
      @import url('https://fonts.googleapis.com/css?family=Merriweather:400,700');
      

      Next, add the following custom variable definitions and styles for the application:

      ~/rails-bootstrap/app/javascript/stylesheets/application.scss

      . . .
      $white: white;
      $black: black;
      
      .navbar {
              margin-bottom: 0;
              background: $black;
      }
      body {
              background: $black;
              color: $white;
              font-family: 'Merriweather', sans-serif;
      }
      h1,
      h2 {
              font-weight: bold;
      }
      p {
              font-size: 16px;
              color: $white;
      }
      a:visited {
              color: $black;
      }
      .jumbotron {
              background: #0048CD;
              color: $white;
              text-align: center;
              p {
                      color: $white;
                      font-size: 26px;
              }
      }
      .link {
              color: $white;
      }
      .btn-primary {
              color: $white;
              border-color: $white;
              margin-bottom: 5px;
      }
      .btn-sm {
              background-color: $white;
              display: inline-block;
      }
      img,
      video,
      audio {
              margin-top: 20px;
              max-width: 80%;
      }
      caption {
      
              float: left;
              clear: both;
      
      }
      

      Save and close the file when you are finished editing.

      You have added Bootstrap to your project, along with some custom styles. Now you can move on to integrating Bootstrap layout conventions and components into your application files.

      Step 4 — Modifying the Application Layout

      Our first step in integrating Bootstrap conventions and components into the project will be adding them to the main application layout file. This file sets the elements that will be included with each rendered view template for the application. In this file, we’ll make sure our webpack entry point is defined, while also adding references to a shared navigation headers partial and some logic that will allow us to render a layout for the views associated with the shark application.

      First, open app/views/layouts/application.html.erb, your application’s main layout file:

      • nano app/views/layouts/application.html.erb

      Currently, the file looks like this:

      ~/rails-bootstrap/app/views/layouts/application.html.erb

      <!DOCTYPE html>
      <html>
        <head>
          <title>Sharkapp</title>
          <%= csrf_meta_tags %>
          <%= csp_meta_tag %>
      
          <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
          <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
        </head>
      
        <body>
          <%= yield %>
        </body>
      </html>
      

      The code renders things like cross-site request forgery protection parameters and tokens for dynamic forms, a csp-nonce for per-session nonces that allows in-line script tags, and the application’s style sheets and javascript assets. Note that rather than having a javascript_link_tag, our code includes a javascript_pack_tag, which tells Rails to load our main webpack entry point at app/javascript/packs/application.js.

      In the <body> of the page, a yield statement tells Rails to insert the content from a view. In this case, because our application root formerly mapped to the index shark view, this would have inserted the content from that view. Now, however, because we have changed the root view, this will insert content from the home controller’s index view.

      This raises a couple of questions: Do we want the home view for the application to be the same as what users see when they view the shark application? And if we want these views to be somewhat different, how do we implement that?

      The first step will be deciding what should be replicated across all application views. We can leave everything included under the <header> in place, since it is primarily tags and metadata that we want to be present on all application pages. Within this section, however, we can also add a few things that will customize all of our application views.

      First, add the viewport meta tag that Bootstrap recommends for responsive behaviors:

      ~/rails-bootstrap/app/views/layouts/application.html.erb

      <!DOCTYPE html>
      <html>
        <head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Sharkapp</title>
          <%= csrf_meta_tags %>
          <%= csp_meta_tag %>
      . . .
      

      Next, replace the existing title code with code that will render the application title in a more dynamic way:

      ~/rails-bootstrap/app/views/layouts/application.html.erb

      <!DOCTYPE html>
      <html>
        <head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title><%= content_for?(:title) ? yield(:title) : "About Sharks" %></title>
          <%= csrf_meta_tags %>
          <%= csp_meta_tag %>
      . . .
      

      Add a <meta> tag to include a description of the site:

      ~/rails-bootstrap/app/views/layouts/application.html.erb

      <!DOCTYPE html>
      <html>
        <head>
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title><%= content_for?(:title) ? yield(:title) : "About Sharks" %></title>
          <meta name="description" content="<%= content_for?(:description) ? yield(:description) : "About Sharks" %>">
          <%= csrf_meta_tags %>
          <%= csp_meta_tag %>
      . . .
      

      With this code in place, you can add a navigation partial to the layout. Ideally, each of our application’s pages should include a navbar component at the top of the page, so that users can easily navigate from one part of the site to another.

      Under the <body> tag, add a <header> tag and the following render statement:

      ~/rails-bootstrap/app/views/layouts/application.html.erb

        <body>
          <header>
            <%= render 'layouts/navigation' %>
          </header>
      
          <%= yield %>
      . . .
      

      This <header> tag allows you to organize your page content, separating the navbar from the main page contents.

      Finally, you can add a <main> element tag and some logic to control which view, and thus which layout, the application will render. This code uses the content_for method to reference a content identifier that we will associate with our sharks layout in the next step.

      Replace the existing yield statement with the following content:

      ~/rails-bootstrap/app/views/layouts/application.html.erb

      . . . 
        <body>
          <header>
            <%= render 'layouts/navigation' %>
          </header>
          <main role="main">
          <%= content_for?(:content) ? yield(:content) : yield %>
          </main>
        </body>
      </html>
      

      Now, if the :content block is set, the application will yield the associated layout. Otherwise, thanks to the ternary operator, it will do an implicit yield of the view associated with the home controller.

      Once you have made these changes, save and close the file.

      With the application-wide layout set, you can move on to creating the shared navbar partial and the sharks layout for your shark views.

      Step 5 — Creating the Shared Partial and Specific Layouts

      In addition to the changes you made to the application layout in the previous Step, you will want to create the shared navbar partial, the sharks layout that you referenced in app/views/layouts/application.html.erb, and a view for the application landing page. You can also add Bootstrap styles to your application’s current link_to elements in order to take advantage of built-in Bootstrap styles.

      First, open a file for the shared navbar partial:

      • nano app/views/layouts/_navigation.html.erb

      Add the following code to the file to create the navbar:

      ~/rails-bootstrap/app/views/layouts/_navigation.html.erb

      <nav class="navbar navbar-dark navbar-static-top navbar-expand-md">
          <div class="container">
              <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span>
              </button> <%= link_to "Everything Sharks", root_path, class: 'navbar-brand' %>
              <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                  <ul class="nav navbar-nav mr-auto">
                  <li class='nav-item'><%= link_to 'Home', home_index_path, class: 'nav-link' %></li>
                  <li class='nav-item'><%= link_to 'Sharks', sharks_path, class: 'nav-link' %></li>  
      
                      </li>
                  </ul>
              </div>
          </div>
      </nav>
      

      This navbar creates a link for the application root using the link_to method, which maps to the application root path. The navbar also includes two additional links: one to the Home path, which maps to the home controller’s index view, and another to the shark application path, which maps to the shark index view.

      Save and close the file when you are finished editing.

      Next, open a file in the layouts directory for the sharks layout:

      • nano app/views/layouts/sharks.html.erb

      Before adding layout features, we will need to ensure that the content of the layout is set as the :content block that we reference in the main application layout. Add the following lines to the file to create the block:

      ~/rails-bootstrap/app/views/layouts/sharks.html.erb

      <% content_for :content do %>
      <% end %>
      

      The code we’re about to write in this block will be rendered inside the :content block in the app/views/layouts/application.html.erb file whenever a sharks view is requested by a controller.

      Next, inside the block itself, add the following code to create a jumbotron component and two containers:

      ~/rails-bootstrap/app/views/layouts/sharks.html.erb

      <% content_for :content do %>
          <div class="jumbotron text-center">
              <h1>Shark Info</h1>
          </div>
          <div class="container">
              <div class="row">
                  <div class="col-lg-6">
                      <p>
                          <%= yield %>
                      </p>
                  </div>
                  <div class="col-lg-6">
                      <p>
      
                          <div class="caption">You can always count on some sharks to be friendly and welcoming!</div>
                          <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                      </p>
      
                  </div>
              </div>
          </div>
          <% end %>
      

      The first container includes a yield statement that will insert the content from the shark controller’s views, while the second includes a reminder that certain sharks are always friendly and welcoming.

      Finally, at the bottom of the file, add the following render statement to render the application layout:

      ~/rails-bootstrap/app/views/layouts/sharks.html.erb

      . . . 
                  </div>
              </div>
          </div>
          <% end %>
              <%= render template: "layouts/application" %>
      

      This sharks layout will provide the content for the named :content block in the main application layout; it will then render the application layout itself to ensure that our rendered application pages have everything we want at the application-wide level.

      Save and close the file when you are finished editing.

      We now have our partials and layouts in place, but we haven’t yet created the view that users will see when they navigate to the application home page, the home controller’s index view.

      Open that file now:

      • nano app/views/home/index.html.erb

      The structure of this view will match the layout we defined for shark views, with a main jumbotron component and two containers. Replace the boilerplate code in the file with the following:

      ~/rails-bootstrap/app/views/home/index.html.erb

      <div class="jumbotron">
          <div class="container">
              <h1>Want to Learn About Sharks?</h1>
              <p>Are you ready to learn about sharks?</p>
              <br>
              <p>
                  <%= button_to 'Get Shark Info', sharks_path, :method => :get,  :class => "btn btn-primary btn-lg"%>
              </p>
          </div>
      </div>
      <div class="container">
          <div class="row">
              <div class="col-lg-6">
                  <h3>Not all sharks are alike</h3>
                  <p>Though some are dangerous, sharks generally do not attack humans. Out of the 500 species known to researchers, only 30 have been known to attack humans.
                  </p>
              </div>
              <div class="col-lg-6">
                  <h3>Sharks are ancient</h3>
                  <p>There is evidence to suggest that sharks lived up to 400 million years ago.
                  </p>
              </div>
          </div>
      </div>
      

      Now, when landing on the home page of the application, users will have a clear way to navigate to the shark section of the application, by clicking on the Get Shark Info button. This button points to the shark_path — the helper that maps to the routes associated with the sharks controller.

      Save and close the file when you are finished editing.

      Our last task will be to transform some of the link_to methods in our application into buttons that we can style using Bootstrap. We will also add a way to navigate back to the home page from the sharks index view.

      Open the sharks index view to start:

      • nano app/views/sharks/index.html.erb

      At the bottom of the file, locate the link_to method that directs to the new shark view:

      ~/rails-bootstrap/app/views/sharks/index.html.erb

      . . .
      <%= link_to 'New Shark', new_shark_path %>
      

      Modify the code to turn this link into a button that uses Bootstrap’s "btn btn-primary btn-sm" class:

      ~/rails-bootstrap/app/views/sharks/index.html.erb

      . . .
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %>
      

      Next, add a link to the application home page:

      ~/rails-bootstrap/app/views/sharks/index.html.erb

      . . .
      <%= link_to 'New Shark', new_shark_path, :class => "btn btn-primary btn-sm" %> <%= link_to 'Home', home_index_path, :class => "btn btn-primary btn-sm" %>
      

      Save and close the file when you are finished editing.

      Next, open the new view:

      • nano app/views/sharks/new.html.erb

      Add the button styles to the link_to method at the bottom of the file:

      ~/rails-bootstrap/app/views/sharks/new.html.erb

      . . . 
      <%= link_to 'Back', sharks_path, :class => "btn btn-primary btn-sm" %>
      

      Save and close the file.

      Open the edit view:

      • nano app/views/sharks/edit.html.erb

      Currently, the link_to methods are arranged like this:

      ~/rails-bootstrap/app/views/sharks/edit.html.erb

      . . . 
      <%= link_to 'Show', @shark %> |
      <%= link_to 'Back', sharks_path %>
      

      Change their arrangement on the page and add the button styles, so that the code looks like this:

      ~/rails-bootstrap/app/views/sharks/edit.html.erb

      . . . 
      <%= link_to 'Show', @shark, :class => "btn btn-primary btn-sm" %> <%= link_to 'Back', sharks_path, :class => "btn btn-primary btn-sm" %>
      

      Save and close the file.

      Finally, open the show view:

      • nano app/views/sharks/show.html.erb

      Find the following link_to methods:

      ~/rails-bootstrap/app/views/sharks/show.html.erb

      . . . 
      <%= link_to 'Edit', edit_shark_path(@shark) %> |
      <%= link_to 'Back', sharks_path %>
      . . . 
      

      Change them to look like this:

      ~/rails-bootstrap/app/views/sharks/show.html.erb

      . . . 
      <%= link_to 'Edit', edit_shark_path(@shark), :class => "btn btn-primary btn-sm" %> <%= link_to 'Back', sharks_path, :class => "btn btn-primary btn-sm" %>
      . . .
      

      Save and close the file.

      You are now ready to test the application.

      Start your server with the appropriate command:

      • rails s if you are working locally
      • rails s --binding=your_server_ip if you are working with a development server

      Navigate to localhost:3000 or http://your_server_ip:3000, depending on whether you are working locally or on a server. You will see the following landing page:

      Application Landing Page

      Click on Get Shark Info. You will see the following page:

      Sharks Index Page

      You can now edit your shark, or add facts and posts, using the methods described in How To Add Stimulus to a Ruby on Rails Application. You can also add new sharks to the conversation.

      As you navigate to other shark views, you will see that the shark layout is always included:

      Sharks Show Page

      You now have Bootstrap integrated into your Rails application. From here, you can move forward by adding new styles and components to your application to make it more appealing to users.

      Conclusion

      You now have Bootstrap integrated into your Rails application, which will allow you to create responsive and visually appealing styles to enhance your users’ experience of the project.

      To learn more about Bootstrap features and what they offer, please see the Bootstrap documentation. You can also look at the documentation for Sass, to get a sense of how you can use it to enhance and extend your CSS styles and logic.

      If you are interested in seeing how Bootstrap integrates with other frameworks, please see How To Build a Weather App with Angular, Bootstrap, and the APIXU API. You can also learn about how it integrates with Rails and React by reading How To Set Up a Ruby on Rails Project with a React Frontend.



      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

      + [email protected] + [email protected] + [email protected] ...

      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