Reputation: 463
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
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
Reputation: 10177
I needed to learn some angular, so I tried to convert this as a learning effort. Step by step:
ng new test
ng generate pipe search
(I found this by listing all things generatable ng generate --help
.import { FormsModule } from "@angular/forms";
and update @NgModule imports: ... imports: [ BrowserModule, FormsModule ], ...
.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.
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
}
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