Matt
Matt

Reputation: 463

How to transition an angularJS application to angular

I currently have a simple search functionality defined in AngularJS (https://next.plnkr.co/edit/qpyvZuvI8vw8q6on?open=lib%2Fscript.js&preview) but I'm hoping to migrate this feature into an existing Angular application. I created a new Angular application and moved the view into 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>

I then moved the controller code into a javascript file called 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: 2,
      course_number: '110 Mathematics',
      title: 'Linear Algebra',
      description: "Matrices, vector spaces, linear transformations, inner products, determinants.",
      keywords: "determinants, forms, products, eigenvectors, linear"
        },
        {
           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 can't access any of the data defined in the controller and the application doesn't work. I'm relatively new to web development, so will this not work because I need to convert my javascript code into typescript? Or do I need to somehow import my code in a different way?

Any input is appreciated! Thank you!

Upvotes: 1

Views: 348

Answers (2)

Anuradha Gunasekara
Anuradha Gunasekara

Reputation: 6983

I've create a working example on stackblitz. Look into app.component.ts , app.component.html, app.module.ts and course-filter.pipe.ts

In Angular there is a thing called Pipes . A pipe takes in data as input and transforms it to a desired output. There are some built in pipes and also you can create your own custom pipes. for your scenario we have to create a custom pipe.

Most of your html can be reused. But you have to replace filter functionality with a Angular pipe.

You have to create a pipe like this and declare it 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)
    });
  }
}

The app.module.ts

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 { }

You add the FormsModule to the list of imports defined in the @NgModule decorator. This gives the application access to all of the template-driven forms features, including ngModel.

BrowserModule is a module that provides all kinds of services and directives one usually wants to use in an Angular2 application like ngIf.

And your template should 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>

Upvotes: 2

ippi
ippi

Reputation: 10177

I needed to learn some angular, so I tried to convert this as a learning effort. Step by step:

  1. Create new project ng new test
  2. There are pipe-functions in angular, but no pipe-filter so we have to create one. (cd test) ng generate pipe search (I found this by listing all things generatable ng generate --help.
  3. After some searching I learned that to use "ng-model" you add the "FormsModule" to your app. In app.module.ts: import { FormsModule } from "@angular/forms"; and update @NgModule imports: ... imports: [ BrowserModule, FormsModule ], ....
  4. Updated app.component.html to use 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 knew how your old template works then I think those changes are self-explanatory. It took a little bit of research, but almost everything is equivalent with AngularJS and has just a few changes to syntax.

  1. Controller as well. No more scope, just declare a variable straight in the controller. And add the search input model of course:

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. And finally implement our search filter. You'll need to put the most work in here (if you want to mimic the old filter exactly anyway.

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 used indexOf and es6 filter to create something simple - here only looking at two fields and it's not case insensitive. I had to set pure to false to get it to update correctly. Which suggests to me that a pipe might not be the optimal way to do things. Perhaps a controller function triggered by model changes (with debounce) and create a results-array would be a better idea.

Additional note: Using NgModel is probably overkill since it binds a value two-ways (from controller to template and from template to controller) but we never change the value (other than to set a default), so skipping ngModel and use (change)="doSearch()" would be one import less, and maybe cleaner but I guess less modular than the filter.

Upvotes: 2

Related Questions