3

I would like to use a service in my class (model)

Use Service in my Class

movie-list.component.ts

import { Component, OnInit } from '@angular/core';
import {Movie} from "../../models/movie";
import {MovieService} from "../../services/movie.service";

@Component({
  selector: 'movie-list',
  templateUrl: './movie-list.component.html',
})

export class MovieListComponent implements OnInit {
  public movies = [];
  public movie: Movie;

  constructor(private movieService: MovieService) {
      this.movie = new Movie();
  }

  ngOnInit() {
    console.log(this.movie.getMovies());
    this.movies = movie.getMovies();
  }
}

movie-list.component.html

<div *ngFor="let movie of movies">
    {{ movie.title }}

    <div *ngFor="let actor of movie.actors">
        {{ actor.name }} - {{ actor.getOscar() }}
    </div>
</div>

movie.ts (movie model)

import { Actor } from "./actor";

export class Movie {

    constructor(title: string = '', actors: any = []) {
        this.title = title;
        this.actors = [];
    }

    title: string = '';
    actors: any = [];
    movieService;

    setModel(obj) {
        Object.assign(this, obj);
    }

    addActor(actor) {
        this.actors.push(actor);
    }

    build(data) {
        let movie = new Movie(
            data.title,
            data.actors
        );

        movie.setModel({ actors: []});

        data.actors.forEach((actor) => {
            let new_actor = new Actor(
                actor.firstname,
                actor.lastname
            );

            movie.addActor(new_actor);
        });

        return movie;
    }

    getMovies() {
        let movies: any[];

        this.movieService.getMovies().subscribe(
            data => {
                data.map((result) => {
                    movies.push(this.build(result));
                });

                return movies;
            },
            error => {
                console.error(error);
            }
        )
    }
}

actor.ts (actor model)

export class Actor {

    constructor(firstname: string, lastname: string) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    firstname: string;
    lastname: string;

    setModel(obj) {
        Object.assign(this, obj);
    }

    getOscar() {
        return '10 oscars';
    };
}

movie.service.ts

import { Injectable } from '@angular/core';
import {Http} from "@angular/http";
import 'rxjs/add/operator/map';

@Injectable()
export class MovieService {
  constructor(private http:Http) { }

    getMovies() {
    return this.http
        .get('data/movies.json')
        .map(res => res.json());
  }

}

movies.json

[
  {
    "title": "title1",
    "actors": [
      {
        "firstname": "firstname10",
        "lastname": "lastname10"
      },
      {
        "firstname": "firstname11",
        "lastname": "lastname11"
      }
    ]
  },
  {
    "title": "title2",
    "actors": [
      {
        "firstname": "firstname20",
        "lastname": "lastname20"
      },
      {
        "firstname": "firstname21",
        "lastname": "lastname21"
      }
    ]
  }
]

I would like to "inject/use" my service in my Class to use this workflow:

  1. Component (call getMovies in my Class)
  2. Class (call getMovies in my service, with response build my object (Movie, Actor) with this.build
  3. Service (call my API, return JSON, without type object (Movie, Actor)

I tried to use @inject in my Class but it doesn't work from this post How to inject Service into class(not component)

I would like to know the best practice to do this.

With this code, I have an error :

enter image description here

Modification

Added this in my component :

constructor(private movieService: MovieService) {
  this.movie = new Movie();
  this.movie.movieService = movieService; // add this line
}

I have an error on this line in my component :

ngOnInit() {
    console.log(this.movie.getMovies());
    this.movies = movie.getMovies(); // error Type 'void' is not assignable to type 'any[]'.
}

I would like to conserve the subscribe in the model to have a clean code in my component

enter image description here

SOLVED

movie-list.component.ts

import { Component, OnInit } from '@angular/core';
import { Movie } from "../../models/movie";
import { MovieService } from "../../services/movie.service";

@Component({
  selector: 'movie-list',
  templateUrl: './movie-list.component.html',
})

export class MovieListComponent implements OnInit {
    public movie: Movie;
    public movies = [];

  constructor(private movieService: MovieService) {
      this.movie = new Movie('', []);
      this.movie.movieService = movieService;
  }

  ngOnInit() {
      this.movies = this.movie.getMovies();
  }
}

movie.ts

import { MovieService } from "../services/movie.service";
import { Actor } from "./actor";

export class Movie {

    constructor(
        private title: string = '',
        private actors: any[] = [],
    ) { }

    public movieService : MovieService;

    setModel(obj) {
        Object.assign(this, obj);
    }

    addActor(actor) {
        this.actors.push(actor);
    }

    build(data) {
        const movie = new Movie(
            data.title,
            data.actors,
        );

        movie.setModel({ actors: [] });

        data.actors.forEach((actor) => {
            const new_actor = new Actor(
                actor.firstname,
                actor.lastname
            );

            movie.addActor(new_actor);
        });

        return movie;
    }

    getMovies() {
        let movies = [];

         this.movieService.getMovies().subscribe(
            data => {
                data.map((result) => {
                    movies.push(this.build(result));
                });
            },
            error => {
                console.error(error);
            }
        );

        return movies;
    }
}
Jérémie Chazelle
  • 1,721
  • 4
  • 32
  • 70
  • amy i ask the need for injecting service in class, as it would just do fine in component. you should use class as model – Rahul Singh Aug 30 '17 at 09:25
  • thx for your comment ! I would like a clean code in my component, for example : without subscribe in my component – Jérémie Chazelle Aug 30 '17 at 09:28
  • Your last error is about the `getMovies()` method that returns nothing. You are trying to return the movies, but you it is in the Subscription, so it doesn't return anything, and thus you cannot affect nothing to `this.movies`. – Alex Beugnet Aug 30 '17 at 11:05
  • @AlexBeugnet yes ! But How can I conserve the subscribe in my model and work it ? – Jérémie Chazelle Aug 30 '17 at 11:09
  • 1
    Well the best thing would be to build your Movie model from your first call, and then return an Observable of that result. You can then subscribe to that observable from your component. There is not much other choice, since the data is asynchronous – Alex Beugnet Aug 30 '17 at 11:55
  • 1
    thx @AlexBeugnet ! I updated my post and validated your precious help ! – Jérémie Chazelle Aug 30 '17 at 12:22

2 Answers2

2

You should read this about Smart and Presentation (Dumb) components. This is by far the best practice there is, as the whole angular data management and detection changes revolves around it.

http://blog.angular-university.io/angular-2-smart-components-vs-presentation-components-whats-the-difference-when-to-use-each-and-why/


Your error comes from the two parameters you set in the constructor, as these should be sent either by the @Input decorator or fetched from a service. You don't do things like let foo = new MovieComponent(.., ...); so this can't work.

Read this from the official documentation. It will also help you for setting up the smart / dumb component structure :

https://angular.io/guide/component-interaction#pass-data-from-parent-to-child-with-input-binding

EDIT : To make your service work, simply pass it as a dependency into your MovieComponent constructor like to :

constructor(
  private movieService: MovieService
) {}

Do not forget to provide your MovieService in your (I expect) MovieModule or wherever the MovieComponent is declared (in the same module)


EDIT2 : Here are the code parts that worked for me. The model class would need to be reworked, with a method to send back the movies built asynchronously, but even then, I'd find that model not very useful. You are better off using only interfaces and a service. Also, this is obviously not a smart/dumb component interaction, so some upgrades will be needed to achieve what you want.

MovieListComponent

export class MovieListComponent implements OnInit {
  public movies = [];
  public movie: Movie;

  constructor(private movieService: MovieService) {
    this.movie = new Movie('', []);
  }

  ngOnInit() {
    this.movie.movieService = this.movieService;
    this.movie.getMovies().subscribe(
      data => {
        data.map((result) => {
          this.movies.push(this.movie.build(result));
        });
      }
    );
  }
}

Movie (model)

export class Movie {
  movies: any[];
  movieService: MovieService

  constructor(
    private title: string = '',
    private actors: any[] = [],
  ) { }

  setModel(obj) {
    Object.assign(this, obj);
  }

  addActor(actor) {
    this.actors.push(actor);
  }

  build(data) {
    const movie = new Movie(
      data.title,
      data.actors,
    );

    movie.setModel({ actors: [] });

    data.actors.forEach((actor) => {
      const new_actor = new Actor(
        actor.firstname,
        actor.lastname
      );

      movie.addActor(new_actor);
    });

    return movie;
  }

  getMovies() {
    return this.movieService.getMovies();
  }
}

Module

@NgModule({
  imports: [
    HttpModule,
    CommonModule
  ],
  declarations: [
    MovieListComponent
  ],
  providers: [
    MovieService
  ]
})
export class MainModule { }
Alex Beugnet
  • 4,003
  • 4
  • 23
  • 40
  • Thx @Alex Beugnet for your comment ! Do you have an exemple to the constructor please ? I removed my constructor in my class to replace with your constructor, but in my component, the instance doesn't work because I don't have the parameter – Jérémie Chazelle Aug 30 '17 at 09:53
  • Since you did everything around your model, you should just change it so that you can create a new instance of your movie class (in that case you will use `new`). See this post which shows a good example : https://stackoverflow.com/questions/38398877/how-do-i-declare-a-model-class-in-my-angular-2-component-using-typescript. Be aware that even though your `MovieListComponent` is still considered a smart component in that case since basically it does (through the link to your Movie Model) the call to the service. – Alex Beugnet Aug 30 '17 at 09:56
  • Thx for your help, I updated my post (component) : I applied your recommandation from your link ! But I always confused to use my service in my class.. – Jérémie Chazelle Aug 30 '17 at 10:09
  • Since you want to make your service call in the model, you need to pass the MovieService as a parameter when you do `new Movie()`, and your `Movie` model class needs to get the reference from the constructor – Alex Beugnet Aug 30 '17 at 10:15
  • Thx @Alex Beugnet ! I updated my post (modification part, bottom post). Can you confirm this syntax please ? Because I can't set my result in an array – Jérémie Chazelle Aug 30 '17 at 10:48
  • I'll edit my answer with my working example. But yeah this is the good syntax. – Alex Beugnet Aug 30 '17 at 10:53
  • To me the model you use is useless, because you can actually build what you want from the MovieService (which is already specific) and send data asynchronously. Thus, you **NEED** to have a component that subscribes (Preferably a smart component), that will send this async data to a dumb component (that displays the movies and actors). So in the end the model is used as a middleware class that builds the data but that's it... – Alex Beugnet Aug 30 '17 at 11:02
-2

pass a parent injector that provides them

constructor( parentInjector:Injector ){

  let injector = ReflectiveInjector.resolveAndCreate ( [ ProductService ] );

  this.productService = injector.get(ProductService, parentInjector);
}
coder
  • 8,346
  • 16
  • 39
  • 53
  • Please edit your answer and format it to clearly show the code. When you are in the editor, you will see on the right side of the screen the help information on how to edit it. – Nic3500 Jun 29 '18 at 00:38