Tips for migrating an AngularJS application to Angular

My current project involves implementing a basic search function using AngularJS (link). I want to integrate this feature into an existing Angular application. To do this, I created a new Angular app and transferred the view to app.component.html.

<head>
    <script src="./script.js"></script>
</head>

<h1> Search Feature </h1>

<body ng-app="search" ng-cloak>
   <div id="content" ng-controller="MainCtrl">
      <input type='text' ng-model='searchText' placeholder=" Enter Query Here  " />  
        <ul>
          <li class="angular-with-newlines" ng-repeat="course in courses | filter:searchText"> 
            {{course.course_number}}: {{course.title}}
            <button ng-click="course.showDesc=!course.showDesc">See More</button> 
            <div ng-if="course.showDesc"> Description: {{course.description}} </div>
       </li>
    </ul>
  </div>
</body>

Next, I moved the controller code to a JavaScript file named script.js.

import angular from 'angular';

angular.module('search', []).controller('MainCtrl', function($scope) {

$scope.courses = [
      {
          id: 1,
    course_number: '54 Mathematics',
    title: 'Linear Algebra and Differential Equations',
    description: 'Basic linear algebra; matrix arithmetic and determinants. Vector spaces; inner product as spaces.',
    keywords: 'determinants, linear, equations, inner, basic, spaces, partial, order'
      },
      {
          id: 3,
    course_number: '89A Statistics',
    title: 'Linear Algebra for Data Science',
    description: 'An introduction to linear algebra for data science.',
    keywords: 'ranking, prob, network, document, algebra, basics, model, matrices,'
      }

  ];
});

However, I am facing issues accessing the data defined in the controller, and the application is not functioning properly. As a beginner in web development, I wonder if the problem lies in needing to convert my JavaScript code to TypeScript or if I need to import my code differently?

I would appreciate any guidance or feedback! Thank you!

Answer №1

Having the need to grasp Angular, I decided to tackle this task as a learning experience by following these steps:

  1. Begin by creating a new project using ng new test
  2. In Angular, there are pipe-functions available but no pipe-filter, so you will have to create one. Navigate to the project directory (cd test) and execute ng generate pipe search. To discover this command, I referred to all generatable options by running ng generate --help.
  3. After some exploration, I discovered that in order to utilize "ng-model," you need to include the "FormsModule" in your application. In app.module.ts, import the module using:
    import { FormsModule } from "@angular/forms";
    and update @NgModule imports with:
    ...  imports: [ BrowserModule, FormsModule ], ...
    .
  4. Modify app.component.html to incorporate our template:

<div id="content">
      <input type='text' [(ngModel)]='searchText' placeholder=" Enter Query Here" />
      <ul>
        <li class="angular-with-newlines" *ngFor="let course of courses | Search:searchText">
            {{course.course_number}}: {{course.title}}
            <button (click)="course.showDesc=!course.showDesc">See More</button> 
            <div *ngIf="course.showDesc"> Description: {{course.description}} </div>
        </li>
      </ul>
    </div>

If you are familiar with how your old template functions, then these modifications should be self-explanatory. It required a bit of research, but most aspects align with AngularJS with just minor syntax changes.

  1. Revamping the Controller is necessary as well. There's no longer scope; instead, simply declare a variable directly within the controller. Don't forget to include the search input model:

import { Component } from "@angular/core";
    @Component({
      selector: "app-root",
      templateUrl: "./app.component.html",
      styleUrls: ["./app.component.css"]
    })
    export class AppComponent {
      title = "app";
      searchText = "Math"; // Just a default value
      courses = [{ id: 1, course_number:  ...}, ...];  // Snip for readability
    }

  1. Lastly, implement the search filter. This step requires more effort, especially if you aim to replicate the old filter precisely.

search.pipe.ts:

import { Pipe, PipeTransform } from "@angular/core";
@Pipe({
  name: "Search",
  pure: false
})
export class SearchPipe implements PipeTransform {
  transform(items: any[], searchValue: string, filter: Object): any {
    return items.filter(item => {
      return item.title.indexOf(searchValue) > -1 ||
             item.course_number.indexOf(searchValue) > -1;
    });
  }
}

I opted for indexOf and es6 filter to keep it simple - focusing only on two fields and without case insensitivity. Setting pure to

false</code was necessary to ensure proper updating. This led me to consider whether a pipe is the best approach. Perhaps having a controller function triggered by model changes (with debounce) to create a results-array might be more efficient.</p>

<p><strong>Additional reminder:</strong> NgModel could be excessive since it binds a value in both directions (from controller to template and vice versa), yet we seldom alter the value (except for setting a default). Skipping ngModel and using <code>(change)="doSearch()"
might reduce complexity and make the code cleaner, albeit less modular compared to the filter.

Answer №2

If you want to see a live example, head over to stackblitz where I have created a working demo. Take a look at app.component.ts, app.component.html, app.module.ts, and course-filter.pipe.ts.

In Angular, there is a concept known as Pipes. Pipes take data as input and transform it into the desired output. There are some built-in pipes, but you can also create your own custom pipes. For this specific case, we need to create a custom pipe.

You can reuse most of your existing HTML code, but you'll need to replace the filter functionality with an Angular pipe.

To do this, you will need to create a pipe similar to the one below and declare it in a ngModule.

import { Pipe, PipeTransform } from '@angular/core';
import { Course } from './app.component';

@Pipe({
  name: 'courseFilter'
})
export class CourseFilter implements PipeTransform {
  transform(courses: Course[], keyword: string): Course[] {
    debugger;
    if (!keyword || keyword === '') {
      return courses;
    }
    return courses.filter((course: Course) => {
      return course.course_number.toString().includes(keyword) ||
        course.description.includes(keyword) || 
        course.keywords.includes(keyword) || 
        course.title.includes(keyword)
    });
  }
}

Make sure to include the pipe in your app.module.ts file.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { HelloComponent } from './hello.component';
import { CourseFilter } from './course-filter.pipe';

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent, CourseFilter ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

Add FormsModule to the list of imports in the @NgModule decorator to access template-driven forms features like ngModel.

BrowserModule provides services and directives needed for an Angular2 application, such as ngIf.

Your template should look something like this:

<h1> Search Feature </h1>

<input type='text' [(ngModel)]='searchText' placeholder=" Search a Topic, Subject, or a Course!" >

<div>
    <ul>
        <li *ngFor="let course of courses | courseFilter: searchText">
       {{course.course_number}} :      {{course.title}} <br>
       <button (click)="course.showDescription = !course.showDescription">See More</button>
       <div *ngIf="course.showDescription">
         {{course.description}}
       </div>
        </li>
    </ul>
</div>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Watching the $scope variable using $watch will result in triggering even when ng-if directive is used, displaying

Encountering an unusual behavior in AngularJS that may appear to be a bug, but there could be a logical explanation. A certain value is being passed to a directive as an attribute. Within this directive, the parameter is being watched using $scope.$watch. ...

Exploring the ng-annotate and resolve features within the ui-router framework

Having an issue with my resolve methods not receiving their injected services. Here is a snippet of my code: (function () { 'use strict'; angular.module('sapphire.sample-limits').config(routes); function routes($stateProv ...

Is it possible for me to display dynamic content and utilize a template engine like (ejs)?

Currently, I am in the process of developing a weather application to enhance my skills with express and ejs. At the moment, I'm utilizing app.get to fetch data from darkSky's API successfully, and I can display it on my index.ejs page. My next ...

Adjust validation message and minimum value when radio button is altered in Angular application

Seeking a way to dynamically set a validation message and minimum value based on a radio button selection. Currently, there are two radio buttons for either 9 or 18 holes, each with a corresponding input (used in a golf handicap calculator app). The goal i ...

Is there a way to transfer parameters from a Vue function to a component?

I am struggling to find the right solution for passing parameters to a function. I need to use NavigateTo to send a String value to my index in order to switch components without using Vue Router. Home.js Vue.component('Home', { props: [&apo ...

Is there a quick way to use AJAX in Rails?

By using the remote:true attribute on a form and responding from the controller with :js, Rails is instructed to run a specific javascript file. For instance, when deleting a user, you would have the User controller with the Destroy action. Then, you woul ...

What is the best way to display a loading animation until the entire wizard has finished loading in the jQuery-steps

I need help with implementing a loading animation using the jQuery-steps plugin in my wizard type form. I want the animation to be displayed until the entire wizard has loaded completely, but I'm unsure of how to enable this feature as there is a labe ...

Having trouble retrieving data passed between functions

One of my Vue components looks like this: import '../forms/form.js' import '../forms/errors.js' export default{ data(){ return{ form: new NewForm({ email: '&apos ...

The most effective method for calculating overhead is through the utilization of synchronous techniques

I'm currently developing a nodeJS app that heavily relies on synchronous methods, particularly for file operations and spawning child processes. I am looking to assess the impact of these blocking main thread activities in terms of overhead. What woul ...

How can I determine if a specific button has been clicked in a child window from a different domain?

Is there a method to detect when a specific button is clicked in a cross-domain child window that cannot be modified? Or determine if a POST request is sent to a particular URL from the child window? Below is an example of the HTML code for the button tha ...

Error message stating 'compression is not defined' encountered while attempting to deploy a Node.js application on Heroku

Why is Heroku indicating that compression is undefined? Strangely, when I manually set process.env.NODE_ENV = 'production' and run the app with node server, everything works perfectly... Error log can be found here: https://gist.github.com/anony ...

Mocking a service dependency in Angular using Jest and Spectator during testing of a different

I am currently using: Angular CLI: 10.2.3 Node: 12.22.1 Everything is working fine with the project build and execution. I am now focusing on adding tests using Jest and Spectator. Specifically, I'm attempting to test a basic service where I can mo ...

Article: Offering CoffeeScript and JavaScript Assets Simultaneously

Currently, my web app is up and running using Node and Express. I initially developed it in JavaScript but now want to transition over to CoffeeScript. My goal is to have both file1.js and file2.coffee coexisting in the application (with both being served ...

Transforming into a serialized division

I am working on creating a custom WISYWIG editor that generates a div with specific inner elements. The goal is to design the div (along with its inner structure), serialize it, store it in a database as a string or JSON format, and later insert it into th ...

Special characters in JSON files may cause loading issues in Nativescript Angular

Can someone help me with an issue I'm having while trying to retrieve a JSON file from the server using a GET method? { "myKey": [{ "code" : "RMB", "symbol" : "¥", }] } When making the Nativescript GET request, ev ...

Copy and paste the code from your clipboard into various input fields

I have been searching for a Vanilla JavaScript solution to copy and paste code into multiple input fields. Although I have found solutions on the internet, they are all jQuery-based. Here is an example of such a jQuery solution <input type="text" maxl ...

Encountering the error message "Fatal error: grunt.util._.contains is not a function" when trying to save code after running

Having both the global and local versions of grunt installed, with grunt-cli v1.2.0 and grunt v1.0.1 respectively, I am looking to uninstall v1.0.1. When I run the 'grunt debug' command, it executes successfully. However, when I try to save the c ...

Securing Node function parameters in an asynchronous environment

I've been grappling with a pressing question lately, and I just can't seem to find a definitive answer. Let me share with you a Node function that I frequently use. It manages web requests and conducts some input/output operations: function han ...

Step-by-step guide on dynamically generating table rows in React

I have been trying to dynamically create a table with rows and columns based on an array. While my rest request is functioning properly, I am facing challenges when attempting to switch from a hard-coded example to a dynamic approach using loops or mapping ...

Keep the code running in JavaScript even in the presence of TypeScript errors

While working with create-react-app and typescript, I prefer for javascript execution not to be stopped if a typescript error is detected. Instead, I would like to receive a warning in the console without interrupting the UI. Is it feasible to adjust the ...