DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • How To Build Web Service Using Spring Boot 2.x
  • Bing Maps With Angular in a Spring Boot Application
  • Spring Data: Data Auditing Using JaVers and MongoDB
  • CRUD Operations on Deeply Nested Comments: Scalable Spring Boot and Spring Data approach

Trending

  • AI-Based Threat Detection in Cloud Security
  • How Trustworthy Is Big Data?
  • Streamlining Event Data in Event-Driven Ansible
  • AI, ML, and Data Science: Shaping the Future of Automation
  1. DZone
  2. Coding
  3. Frameworks
  4. An Angular Autocomplete From UI to DB

An Angular Autocomplete From UI to DB

A developer shows a big of code he's written for Angular and Spring Boot that he has used to create a full stack web application.

By 
Sven Loesekann user avatar
Sven Loesekann
·
Apr. 30, 18 · Tutorial
Likes (12)
Comment
Save
Tweet
Share
29.2K Views

Join the DZone community and get the full member experience.

Join For Free

The MovieManager project is used to manage my collection of movies and uses Angular and Spring Boot with PostgreSQL to do it.

The project is used to show how to build the autocomplete search box for the movie titles. The autocomplete box uses Bootstrap 4 for styling and Angular on the front-end and Spring Boot with PostgreSQL on the backend. The focus is on how to use native Angular/RxJS together with Spring Boot and a relational database.

The Front-End

The front-end uses Angular with RxJS and Bootstrap 4 for styling. Angular and RxJS have the features to create an input and connect it to the backend REST service.

The result of the service is then displayed and updated in a div below the input after every keystroke. Bootstrap provides the CSS classes to style the components.

The front-end has three parts:

  • The template for the div with an input tag and the output div.
  • The component class with form control to connect the input tag with the service method.
  • The service class that makes the REST request to the server.

In the Search Component, I've used the following template:

<div class="container-fluid">
    <div class="row">
...
        <div class="col-3">
            <div class="input-group-prepend">
                <span class="input-group-text" id="searchMovieTitle">Searchfor Movie Title</span> 
                <input type="text" class="form-control" placeholder="title" aria-describedby="searchMovieTitle" [formControl]="movieTitle">
            </div>
            <div *ngIf="movieTitle.value" class="searchList">
                <span *ngIf="moviesLoading">Loading</span>
                <a class="dropdown-item" [routerLink]="['/movie',movie.id]" *ngFor="let movie of movies | async">{{movie.title}}</a>
            </div>
        </div>
        ...
    </div>
</div>

Lines 1-2 create a Bootstrap container of variable width and a row.

Line 4 creates the Bootstrap column for the Autocomplete feature.

Lines 5-8 create an input group that displays a styled input with a label and links the Angular form control movieTitle to the input.

Line 9 creates the result div if something is typed in the input.

Line 10 shows the text 'Loading' if the service is retrieving the movies from the server.

Line 11 shows router links for the movies that have been loaded from the server. The routerLink is the route to the Movie Component with the movie id. *ngFor iterates over the movies that the server sends and async means that the movies are an observable whose data is displayed on arrival. The text of the link is the movie title.

In the search component I've used the following class:

@Component({
  selector: 'app-search',
  templateUrl: './search.component.html',
  styleUrls: ['./search.component.scss']
})
export class SearchComponent implements OnInit {

  generes: Genere[];
  movieTitle = new FormControl();
  movies: Observable<Movie[]>;
  movieActor = new FormControl();
  actors: Observable<Actor[]>;
  importMovies: Movie[] = [];
  importMovieTitle = new FormControl();
  actorsLoading = false;
  moviesLoading = false;
  importMoviesLoading = false;
  showMenu = false;
  moviesByGenere: Movie[] = [];
  moviesByGenLoading = false;  

  constructor(private actorService: ActorsService, private movieService: MoviesService, private userService: UsersService) { }

  ngOnInit() {      
      this.actors = this.movieActor.valueChanges
        .debounceTime(400)
        .distinctUntilChanged()
        .do(() => this.actorsLoading = true)
        .switchMap(name => this.actorService.findActorByName(name))
        .do(() => this.actorsLoading = false);
      this.movies = this.movieTitle.valueChanges
        .debounceTime(400)
        .distinctUntilChanged()
        .do(() => this.moviesLoading = true)
        .switchMap(title => this.movieService.findMovieByTitle(title))
        .do(() => this.moviesLoading = false);
        this.userService.allGeneres().subscribe(res => this.generes = res);
  }

Lines 1-6 create the Search component with the annotation and OnInit interface.

Line 9 creates the movieTitle form control for the user input.

Line 10 declares the movie observables for the server result.

Line 16 creates the moviesLoading boolean to show the loading text.

Line 22 declares the constructor and gets the MovieService and others injected by Angular.

Line 24 declares the ngOnInit method to initialize form controls with the services and set the genres.

Line 31 links the movieTitle form control with the valueChanges observable and the result Observable movies.

Lines 32-33 add a 400 ms timeout between requests to the backend and only send requests if the input has changed.

Line 34 sets the boolean that shows the loading text in the template.

Line 35 uses switchMap to discard the running requests and sends a new request to the service.

Line 36 sets the boolean that sets the loading text to false.

This is the movie service that manages all the movie data:

@Injectable()
export class MoviesService {
    private _reqOptionsArgs = { headers: new HttpHeaders().set( 'Content-Type', 'application/json' ) };

    constructor(private http: HttpClient) { }

  ...

    public findMovieByTitle(title: string) :Observable<Movie[]> {
        if(!title) {
            return Observable.of([]);
        }
        return this.http.get('/rest/movie/'+title, this._reqOptionsArgs).catch(error => {
            console.error( JSON.stringify( error ) );
            return Observable.throw( error );
            });
    }

    ...
}

Lines 1-2 creates the MoviesService with the annotation.

Line 3 creates the HTTP headers.

Line 5 is the constructor that gets the HttpClient that's injected.

Line 9 declares the method findMoveByTitle that returns an Observable movie array.

Lines 10-12 return an empty Observable if the title is empty.

Lines 13-16 send the HTTP request to the server, catch server errors, log them, and throw an error.

The Backend

The backend is done in Spring Boot with JPA and PostgreSQL. It uses Spring Boot features to serve a REST interface and a service to manage the repositories and create transactions around service calls. The transactions are needed to support the importation and deletion of functions for movies in the MovieManager.

The repository uses JPA to create a query that finds the movie titles of the movies of the user that contains (case sensitive) the title string, that the user typed.

The REST service is created in this class:

@RestController
@RequestMapping("rest/movie")
public class MovieController {
   @Autowired
   private MovieManagerService service;

    ...

   @RequestMapping(value="/{title}", method=RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
   public ResponseEntity<List<MovieDto>> getMovieSearch(@PathVariable("title") String titleStr) throws InterruptedException {
      List<MovieDto> movies = this.service.findMovie(titleStr);
      return new ResponseEntity<List<MovieDto>>(movies, HttpStatus.OK);
   }

...
}

Lines 1-3 declare the MovieController as Restcontroller with the RequestMapping.

Lines 5-6 have the MovieManagerService injected. This manages the transactions and is the common service for movie data.

Lines 10-11 set up the RequestMapping and get the title value of the request string.

Line 12 reads the movie list from the MovieManagerService for this title string.

Line 13 wraps the movie list in a ResponseEntity and returns it.

The main movie service is created in this class:

@Transactional
@Service
public class MovieManagerService {
   @Autowired
   private CrudMovieRepository crudMovieRep;
   @Autowired
   private CrudCastRepository crudCastRep;
   @Autowired
   private CrudActorRepository crudActorRep;
   @Autowired
   private CrudGenereRepository crudGenereRep;
   @Autowired
   private CrudUserRepository crudUserRep;
   @Autowired
   private CustomRepository customRep;
   @Autowired
   private AppUserDetailsService auds;

  ...

   public List<MovieDto> findMovie(String title) {
      List<MovieDto> result = this.customRep.findByTitle(title).stream()
         .map(m -> Converter.convert(m)).collect(Collectors.toList());
      return result;
   }

  ...
}

Lines 1-3 declare the MovieManagerService with the Service annotation to make the class injectable and the Transactional annotation wraps a transaction around the service requests. The transactions are needed for importing or deleting movies in the DB.

Lines 14-15 get the CustomRepository injected. That is the repo for all the non-CRUD DB requests.

Lines 21-22 declare the findMovie method that is used by the RestController and gets the movies for the title string from the customRepo.

Lines 23-24 convert the List of Movie entities in Movie Dtos that can be serialized by the RestController and returns the Dto List.

The custom repository is created in this class:

@Repository
public class CustomRepository {
    @PersistenceContext
    private EntityManager em;
    @Autowired
    private CrudUserRepository crudUserRep;

    ...

    public List<Movie> findByTitle(String title) {
       User user = getCurrentUser();
       List<Movie> results = em.createQuery("select e from Movie e join e.users u where e.title like :title and u.id = :userid", Movie.class).setParameter("title", "%"+title+"%").setParameter("userid", user.getId()).getResultList();
       return results;
    }

    ...
}

Line 1-2 declare the CustomRepository class and and add the Repository annotation to make the repository injectable.

Line 3-4 get the EntitiyManager of Jpa injected.

Line 5-6 get the CrudUserRepository injected to access the current logged in user. The current user is needed for the multi user support.

Line 10-11 declare the findByTitle method that is used in the MovieManagerService and get the current user.

Line 12-13 queries the EntityManager for the movies where the movie title contains the title string and the current user is the owner of the movie. For that the Movie and User entities need to be joined and then filtered by movie title and userId. Then the result is returned.

Summary

Angular and RxJs have the features to create an autocomplete in a few lines of code. Features like timeout between keystrokes, no double requests, and the discarding of stale results are supported by native features. The form control linked to the valueChanges observable does the updating of the results in the div.

DZone has a nice article that describes Angular and some of its features. A lot of the features like dependency injection, static typing, and annotations are familiar to Java devs and make Angular easy to learn. That makes Angular a very good framework for Java devs that are interested in front-end development.

The backend is a small REST Controller with a service and a repository with JPA. Spring Boot makes it possible to create the backend with few lines of code and a few annotations.

AngularJS Spring Framework Database Spring Boot Relational database Requests

Opinions expressed by DZone contributors are their own.

Related

  • How To Build Web Service Using Spring Boot 2.x
  • Bing Maps With Angular in a Spring Boot Application
  • Spring Data: Data Auditing Using JaVers and MongoDB
  • CRUD Operations on Deeply Nested Comments: Scalable Spring Boot and Spring Data approach

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!