Add New Documentary Series

Here’s the code for creating new documentary series.

import { YoutubeService } from './../../../../services/youtube.service';
import { OMDBService } from './../../../../services/omdb.service';
import { AngularEditorConfig } from '@kolkov/angular-editor';
import { FormBuilder, FormControl, Validators, FormArray, FormGroup } from '@angular/forms';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CategoryService } from './../../../../services/category.service';
import { VideoSourceService } from './../../../../services/video-source.service';
import { HttpParams } from '@angular/common/http';
import { YearService } from './../../../../services/year.service';
import { UserService } from './../../../../services/user.service';
import { DocumentaryService } from './../../../../services/documentary.service';
import { Router, ActivatedRoute } from '@angular/router';
import { Episodic } from './../../../../models/episodic.model';
import { Documentary } from 'src/app/models/documentary.model';
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Location } from "@angular/common";

@Component({
  selector: 'app-documentary-add-episodic',
  templateUrl: './documentary-add-episodic.component.html',
  styleUrls: ['./documentary-add-episodic.component.css']
})
export class DocumentaryAddEpisodicComponent implements OnInit {

  private documentary: Documentary;

  private form: FormGroup;
  private imdbForm: FormGroup;
  private youtubeForm: FormGroup;

  private submitted = false;
  private errors = null;

  private posterImgURL;
  private wideImgURL;
  private thumbnailImgURLDict = {};

  private page;
  private slug;
  private editMode = false;

  private me;

  private config: any;

  private seasonIndex;
  private episodeIndex;

  editorConfig: AngularEditorConfig = {
    editable: true,
    spellcheck: true,
    height: '25rem',
    minHeight: '5rem',
    placeholder: 'Enter text here...',
    translate: 'no',
    uploadUrl: 'v1/images', // if needed
  };

  private showPage = false;
  private hasToggledForm = false;
  private showForm = false;
  private showAddTitleButton = true;
  private showDocumentaries = true;

  private isFetchingYears = false;
  private isFetchingVideoSources = false;
  private isFetchingCategories = false;
  private isFetchingDocumentaries = false;

  private isFetchingDocumentariesFromIMDB = false;
  private isFetchingDocumentaryFromIMDB = false;
  private showSearchedDocumentariesFromIMDB = false;
  private showSearchedDocumentaryFromIMDB = false;

  private searchedDocumentariesFromIMDB;
  private searchedDocumentaryFromIMDB;

  private isFetchingVideosFromYoutube = false;
  private showSearchedVideosFromYoutube = false;
  private searchedVideosFromYoutube;

  private years;
  private videoSources;
  private categories;

  private myDocumentaries;

  private queryParamsSubscription;
  private routeParamsSubscription;
  private documentaryBySlugSubscription;
  private meSubscription;
  private videoSourcesSubscription;
  private categoriesSubscription;
  private getByImdbIdSubscription;
  private ombdSearchSubscription;
  private youtubeByIdSubscription;
  private youtubeSearchSubscription;

  private closeResult: string;

  private seasonNumber = 1;
  private episodeNumber = 1;

  private imdbType;

  constructor(
    private documentaryService: DocumentaryService,
    private userService: UserService,
    private yearService: YearService,
    private videoSourceService: VideoSourceService,
    private categoryService: CategoryService,
    private omdbService: OMDBService,
    private youtubeService: YoutubeService,
    private route: ActivatedRoute,
    private cd: ChangeDetectorRef,
    private router: Router,
    private location: Location,
    private modalService: NgbModal,
    private fb: FormBuilder
  ) { }

  ngOnInit() {
    this.initModel();

    this.queryParamsSubscription = this.route
      .queryParams
      .subscribe(params => {
        this.page = +params['page'] || 1;

        this.routeParamsSubscription = this.route
          .paramMap
          .subscribe(params => {
            this.slug = params['params']['slug'];
            this.editMode = this.slug != null;

            if (this.editMode) {
              this.documentaryBySlugSubscription = this.documentaryService
                .getDocumentaryBySlug(this.slug)
                .subscribe((result: any) => {
                  this.documentary = result;
                  this.toggleForm();
                  this.showPage = true;
                });
            } else {
              this.meSubscription = this.userService
                .getMe()
                .subscribe(me => {
                  this.me = me;

                  if (!this.hasToggledForm) {
                    this.fetchDocumentaries();
                    this.showPage = true;
                  }
                })
            }
          })
      })
  }

  initModel() {
    this.documentary = new Documentary();
    let episodic = new Episodic();
    this.documentary.episodic = episodic;
  }

  toggleForm() {
    this.showAddTitleButton = false;
    this.showDocumentaries = false;

    this.showForm = !this.showForm;

    this.initYears();
    this.initVideoSources();
    this.initCategories();
    this.initForm();
  }

  initForm(seasons = null) {
    let title = this.documentary.title;
    let category = this.documentary.category;
    let storyline = this.documentary.storyline;
    let summary = this.documentary.summary;
    // let videoSource = null;
    //if (this.documentary.standalone.videoSource) {
    //  videoSource = this.documentary.standalone.videoSource.id
    // }
    //let videoId = this.documentary.standalone.videoId;
    let year = this.documentary.year;
    let yearFrom = this.documentary.yearFrom;
    let yearTo = this.documentary.yearTo;
    //let length = this.documentary.length;
    let poster = this.documentary.poster;
    this.posterImgURL = this.documentary.poster;
    let wideImage = this.documentary.wideImage;
    this.wideImgURL = this.documentary.wideImage;
    let imdbId = this.documentary.imdbId;

    this.form = this.fb.group({
      'title': new FormControl(title, [Validators.required]),
      'category': new FormControl(category, [Validators.required]),
      'storyline': new FormControl(storyline, [Validators.required]),
      'summary': new FormControl(summary, [Validators.required]),
      'yearFrom': new FormControl(yearFrom, [Validators.required]),
      'yearTo': new FormControl(yearTo, [Validators.required]),
      'poster': new FormControl(poster, [Validators.required]),
      'wideImage': new FormControl(wideImage, [Validators.required]),
      'imdbId': new FormControl(imdbId),
      'seasons': this.fb.array([], Validators.required)
    });

    if (seasons != null) {
      seasons.forEach(season => {
        this.addSeason(season);
      });
    }

    console.log("this.thumbnailImgURLDict");
    console.log(this.thumbnailImgURLDict);
  }

  getEpisodeNumber(episode) {
    return episode.value.number;
  }

  getSeasonNumber(season) {
    return season.value.number;
  }

  addSeason(season = null) {
    if (season != null) {
      this.seasonNumber = season.number;
    }

    let control = <FormArray>this.form.controls.seasons;
    control.push(
      this.fb.group({
        'number': new FormControl(this.seasonNumber, [Validators.required]),
        'episodes': this.fb.array([], Validators.required)
      })
    );

    if (season != null) {
      let episodes = season.episodes;
      if (season != null && episodes != null) {
        episodes.forEach(episode => {
          let episodesControl = control.at(this.seasonNumber - 1).get('episodes');
          this.addEpisode(episodesControl, season, episode);
        })
      }
    }

    if (season == null) {
      this.seasonNumber++;
    }
  }

  deleteSeason(number) {
    var seasonsFormArray = this.form.get("seasons") as FormArray;

    let index = 0;
    seasonsFormArray.value.forEach(seasonArray => {
      if (number == seasonArray.number) {
        seasonsFormArray.removeAt(index);
        return;
      }
      index++;
    });
  }

  deleteEpisode(control, index) {
    control.removeAt(index);
  }

  insertEpisode(control, index) {
    let title;
    let storyline;
    let summary;
    let year;
    let length;
    let imdbId;
    let videoId;
    let videoSource = 2;
    let thumbnail;
    let episodeNumber;

    control.insert(index,
      this.fb.group({
        'number': new FormControl(episodeNumber, [Validators.required]),
        'title': new FormControl(title, [Validators.required]),
        'imdbId': new FormControl(imdbId),
        'storyline': new FormControl(storyline, [Validators.required]),
        'summary': new FormControl(summary, [Validators.required]),
        'length': new FormControl(length, [Validators.required]),
        'year': new FormControl(year, [Validators.required]),
        'videoSource': new FormControl(videoSource, [Validators.required]),
        'videoId': new FormControl(videoId, [Validators.required]),
        'thumbnail': new FormControl(thumbnail, [Validators.required]),
      }));
  }

  addEpisode(control, season, episode = null) {
    let title;
    let storyline;
    let summary;
    let year;
    let length;
    let imdbId;
    let videoId;
    let videoSource = 2;
    let thumbnail;
    let episodeNumber;

    if (episode != null) {
      episodeNumber = episode.number;
      title = episode.title;
      imdbId = episode.imdbId;
      thumbnail = episode.thumbnail;
      summary = episode.summary;
      storyline = episode.storyline;
      year = episode.year;
      videoId = episode.videoId;
      videoSource = 2;
      length = episode.length;

      if (this.thumbnailImgURLDict[season.number] == undefined) {
        this.thumbnailImgURLDict[season.number] = {};
      }
      if (this.thumbnailImgURLDict[season.number][episode.number] == undefined) {
        this.thumbnailImgURLDict[season.number][episode.number] = {};
      }
      this.thumbnailImgURLDict[season.number][episode.number] = thumbnail;
    }

    control.push(
      this.fb.group({
        'number': new FormControl(episodeNumber, [Validators.required]),
        'title': new FormControl(title, [Validators.required]),
        'imdbId': new FormControl(imdbId),
        'storyline': new FormControl(storyline, [Validators.required]),
        'summary': new FormControl(summary, [Validators.required]),
        'length': new FormControl(length, [Validators.required]),
        'year': new FormControl(year, [Validators.required]),
        'videoSource': new FormControl(videoSource, [Validators.required]),
        'videoId': new FormControl(videoId, [Validators.required]),
        'thumbnail': new FormControl(thumbnail, [Validators.required]),
      }));

  }

  get f() { return this.form.controls; }

  getThumbnailForSeasonAndEpsiode(seasonNumber: number, episodeNumber: number) {
    if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
      this.thumbnailImgURLDict[seasonNumber] = {};
    }
    return this.thumbnailImgURLDict[seasonNumber][episodeNumber];
  }

  onThumbnailChange(event, seasonIndex, episodeIndex) {
    console.log(event);
    let reader = new FileReader();

    if (event.target.files && event.target.files.length) {
      const [file] = event.target.files;
      reader.readAsDataURL(file);

      reader.onload = () => {
        var seasonsFormArray = this.form.get("seasons") as FormArray;
        var episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;
        episodesFormArray.at(episodeIndex)['controls']['thumbnail'].patchValue(reader.result);

        let seasonNumber = seasonsFormArray.at(seasonIndex).value.number;
        let episodeNumber = episodesFormArray.at(episodeIndex).value.number;

        if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
          this.thumbnailImgURLDict[seasonNumber] = {};
        }
        this.thumbnailImgURLDict[seasonNumber][episodeNumber] = reader.result;
      }

      this.cd.markForCheck();

    };
  }

  openIMDBModal(content, imdbType) {
    console.log(imdbType);
    this.imdbType = imdbType;
    this.initIMDBFrom();
    console.log(content);
    this.modalService.open(content, { ariaLabelledBy: 'modal-omdb' }).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`;
    });
  }


  initIMDBFrom() {
    let title = this.form.value.title;

    this.imdbForm = new FormGroup({
      'title': new FormControl(title, [Validators.required])
    });
  }

  imdbView(imdbId) {
    console.log("imdbId");
    console.log(imdbId);
    this.isFetchingDocumentaryFromIMDB = true;
    this.showSearchedDocumentariesFromIMDB = false;
    this.showSearchedDocumentaryFromIMDB = true;

    let imdbType = this.imdbType;
    this.omdbService.getByImdbId(imdbId, imdbType)
      .subscribe((result: any) => {
        this.searchedDocumentaryFromIMDB = result;
        this.isFetchingDocumentaryFromIMDB = false;
        console.log("searchedDocumentaryFromIMDB");
        console.log(this.searchedDocumentaryFromIMDB);
      })
  }

  imdbSelect() {
    let selectedDocumentary = this.searchedDocumentaryFromIMDB;
    console.log("selectedDocumentary");
    console.log(selectedDocumentary);
    console.log("this.form");
    console.log(this.form);
    if (this.form.value.title == null) {
      this.documentary.title = selectedDocumentary.title;
    } else {
      this.documentary.title = this.form.value.title;
    }

    if (this.form.value.imdbId != selectedDocumentary.imdbId) {
      this.documentary.imdbId = selectedDocumentary.imdbId;
    }

    if (this.form.value.storyline == null) {
      this.documentary.storyline = selectedDocumentary.storyline;
    } else {
      this.documentary.storyline = this.form.value.storyline;
    }

    if (this.form.value.yearFrom == null) {
      this.documentary.yearFrom = selectedDocumentary.yearFrom;
    } else {
      this.documentary.yearFrom = this.form.value.yearFrom;
    }

    if (this.form.value.yearTo == null) {
      this.documentary.yearTo = selectedDocumentary.yearTo;
    } else {
      this.documentary.yearTo = this.form.value.yearTo;
    }

    if (this.form.value.poster == null) {
      this.documentary.poster = selectedDocumentary.poster;
      this.posterImgURL = selectedDocumentary.poster;
    } else {
      this.documentary.poster = this.form.value.poster;
      this.posterImgURL = this.form.value.poster;
    }

    let seasons = selectedDocumentary.seasons;
    this.initForm(seasons);

    this.modalService.dismissAll();
  }

  searchOMDB() {
    this.isFetchingDocumentariesFromIMDB = true;
    this.showSearchedDocumentaryFromIMDB = false;
    this.showSearchedDocumentariesFromIMDB = true;

    let titleOrId = this.imdbForm.value.title;
    let imdbType = this.imdbType;

    this.getByImdbIdSubscription = this.omdbService.getByImdbId(titleOrId, imdbType)
      .subscribe((result: any) => {
        result = [result];
        this.searchedDocumentariesFromIMDB = result;
        this.isFetchingDocumentariesFromIMDB = false;
      },
        (error) => {
          this.ombdSearchSubscription = this.omdbService.getSearchedDocumentaries(titleOrId, imdbType)
            .subscribe((result: any) => {
              this.searchedDocumentariesFromIMDB = result;
              this.isFetchingDocumentariesFromIMDB = false;
            });
        });
  }


  openYoutubeModal(content, seasonIndex: number, episodeIndex: number) {
    this.seasonIndex = seasonIndex;
    this.episodeIndex = episodeIndex;

    this.initYoutubeForm();

    this.modalService.open(content, { ariaLabelledBy: 'modal-youtube' }).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`;
    });
  }

  initYoutubeForm() {
    let seasonIndex = this.seasonIndex;
    let episodeIndex = this.episodeIndex;

    let seasonsFormArray = this.form.get("seasons") as FormArray;
    let episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;
    let titleOrId = episodesFormArray.at(episodeIndex)['controls']['title'].value;

    this.youtubeForm = new FormGroup({
      'title': new FormControl(titleOrId, [Validators.required])
    });
  }

  searchYoutube() {
    this.isFetchingVideosFromYoutube = true;
    this.showSearchedVideosFromYoutube = true;

    let titleOrId = this.youtubeForm.value.title;

    this.youtubeByIdSubscription = this.youtubeService.getById(titleOrId)
      .subscribe((result: any) => {
        this.searchedVideosFromYoutube = result['items'];
        this.isFetchingVideosFromYoutube = false;
      }, (error) => {
        this.youtubeSearchSubscription = this.youtubeService.getSearchedDocumentaries(titleOrId)
          .subscribe((result: any) => {
            this.searchedVideosFromYoutube = result['items'];
            this.isFetchingVideosFromYoutube = false;
          });
      });
  }

  youtubeSelect(selectedVideo) {
    let seasonIndex = this.seasonIndex;
    let episodeIndex = this.episodeIndex;

    let seasonsFormArray = this.form.get("seasons") as FormArray;
    let episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;

    let title = episodesFormArray.at(episodeIndex)['controls']['title'].value;
    if (title == null || !title.trim()) {
      let selectedTitle = selectedVideo.snippet.title;
      episodesFormArray.at(episodeIndex)['controls']['title'].patchValue(selectedTitle);
    }

    let videoId = episodesFormArray.at(episodeIndex)['controls']['videoId'].value;
    if (videoId == null || !videoId.trim()) {
      let selectedVideoId = selectedVideo.id.videoId;
      episodesFormArray.at(episodeIndex)['controls']['videoId'].patchValue(selectedVideoId);
    }

    let thumbnail = episodesFormArray.at(episodeIndex)['controls']['thumbnail'].value;
    if (thumbnail == null || !thumbnail.trim()) {
      let selectedThumbnail = selectedVideo.snippet.thumbnails.default.url;
      episodesFormArray.at(episodeIndex)['controls']['thumbnail'].patchValue(selectedThumbnail);

      let seasonNumber = seasonsFormArray.at(seasonIndex).value.number;
      let episodeNumber = episodesFormArray.at(episodeIndex).value.number;

      if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
        this.thumbnailImgURLDict[seasonNumber] = {};
      }
      this.thumbnailImgURLDict[seasonNumber][episodeNumber] = selectedThumbnail;
    }

    this.episodeIndex = null;
    this.seasonIndex = null;
    this.modalService.dismissAll();
  }

  fetchDocumentaries() {
    if (this.editMode) {
      this.showDocumentaries = false;
      return;
    }

    this.isFetchingDocumentaries = true;

    let params = new HttpParams();

    params = params.append('page', this.page.toString());

    this.location.go(this.router.url.split("?")[0], params.toString());

    this.documentaryService.getMyEpisodicDocumentaries(params, this.me.username)
      .subscribe(result => {
        this.config = {
          itemsPerPage: 5,
          currentPage: this.page,
          totalItems: result['count_results']
        };
        this.myDocumentaries = result['items'];

        this.isFetchingDocumentaries = false;
        this.showDocumentaries = true;
        this.showAddTitleButton = true;
      })
  }

  pageChanged(event) {
    this.config.currentPage = event;
    this.page = event;
    this.fetchDocumentaries();
  }

  initYears() {
    this.isFetchingYears = true;

    this.years = this.yearService.getAllYearsForForm();

    this.isFetchingYears = false;
  }

  initVideoSources() {
    this.isFetchingVideoSources = true;

    let params: HttpParams;
    this.videoSourcesSubscription = this.videoSourceService.getAll(params)
      .subscribe(result => {
        this.videoSources = result;
        console.log("this.videoSources");
        console.log(this.videoSources);

        this.isFetchingVideoSources = false;
      });
  }

  initCategories() {
    this.isFetchingCategories = true;

    let params: HttpParams;
    this.categoriesSubscription = this.categoryService.getAll(params)
      .subscribe(result => {
        this.categories = result;

        this.isFetchingCategories = false;
      });
  }

  onSubmit() {
    this.errors = null;

    console.log(this.f);
    console.log(this.form.value);

    this.submitted = true;

    let formValue = this.form.value;

    if (formValue.seasons.length === 0) {
      this.errors = "You must add a season";
    }

    if (formValue.seasons[0].episodes.length === 0) {
      this.errors = "You must add an episode";
    }

    if (!this.form.valid) {
      return;
    }

    this.errors = null;

    if (this.editMode) {
      this.documentaryService.editEpisodicDocumentary(this.documentary.id, formValue)
        .subscribe((result: any) => {
          //this.reset();
          this.router.navigate(["/add/episodic"]);
        },
          (error) => {
            console.log(error);
            this.errors = error.error;
          });
    } else {
      this.documentaryService.createEpisodicDocumentary(formValue)
        .subscribe((result: any) => {
          //this.reset();
          this.router.navigate(["/add/episodic"]);
        },
          (error) => {
            console.log(error);
            this.errors = error.error;
          });
    }
  }

  ngOnDestroy() {
    this.queryParamsSubscription.unsubscribe();
    this.routeParamsSubscription.unsubscribe();
    if (this.documentaryBySlugSubscription != null) {
      this.documentaryBySlugSubscription.unsubscribe();
    }
    if (this.meSubscription != null) {
      this.meSubscription.unsubscribe();
    }
    if (this.getByImdbIdSubscription != null) {
      this.getByImdbIdSubscription.unsubscribe();
    }
    if (this.ombdSearchSubscription != null) {
      this.ombdSearchSubscription.unsubscribe();
    }
    this.meSubscription.unsubscribe();
  }
}
 

Admin Documentaries with Query Params

See also: The Criteria Pattern

import { Status } from './../../models/status.model';
import { CategoryService } from './../../services/category.service';
import { VideoSourceService } from './../../services/video-source.service';
import { VideoSource } from './../../models/video-source.model';
import { ActivatedRoute, Router } from '@angular/router';
import { DocumentaryService } from './../../services/documentary.service';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpParams } from '@angular/common/http';
import { Documentary } from './../../models/documentary.model';
import { Location } from "@angular/common";
import { Category } from 'src/app/models/category.model';

@Component({
  selector: 'app-admin-documentaries',
  templateUrl: './admin-documentaries.component.html',
  styleUrls: ['./admin-documentaries.component.css']
})
export class AdminDocumentariesComponent implements OnInit, OnDestroy {
  
  private documentariesSubscription;
  private queryParamsSubscription;
  private videoSourcesSubscription;
  private categoriesSubscription;
  public documentaries: Array<Documentary>;
  public videoSources: Array<VideoSource>;
  public categories: Array<Category>;
  public statuses: Array<Status> = [
    { id: 'pending', name: 'Pending' },
    { id: 'publish', name: 'Published' }
  ];
  public featuredOptions = [
    { id: true },
    { id: false }
  ];
  config: any;
  private page;
  private videoSource;
  private previousVideoSource;
  private previousCategory;
  private category;
  private status;
  private previousStatus;
  private featured;
  private previousFeatured;

  constructor(
    private service: DocumentaryService,
    private videoSourceService: VideoSourceService,
    private categoryService: CategoryService,
    private route: ActivatedRoute,
    private location: Location,
    private router: Router) { }

  ngOnInit() {
    this.queryParamsSubscription = this.route
      .queryParams
      .subscribe(params => {
        this.page = +params['page'] || 1;
        this.videoSource = +params['videoSource'] || 'all';
        this.category = +params['category'] || 'all';
        this.status = params['status'] || 'all';
        this.featured = params['featured'] || 'all';
        this.fetchVideoSources();
        this.fetchCategories();
        this.fetchDocumentaries();
      })
  }

  fetchDocumentaries() {
    let params = new HttpParams();
    if (this.videoSource) {
      if (this.videoSource != 'all') {
        params = params.append('videoSource', this.videoSource.toString());
        if (this.videoSource != this.previousVideoSource) {
          this.page = 1;
        }
     }
      this.previousVideoSource = this.videoSource;
    }
    if (this.category) {
      if (this.category != 'all') {
        params = params.append('category', this.category.toString());
        if (this.category != this.previousCategory) {
          this.page = 1;
        }
      }
      this.previousCategory = this.category;
    }
    if (this.status) {
      if (this.status != 'all') {
        params = params.append('status', this.status.toString());
        if (this.status != this.previousStatus) {
          this.page = 1;
        }
      }
      this.previousStatus = this.status;
    }
    if (this.featured) {
      if (this.featured != 'all') {
        params = params.append('featured', this.featured.toString());
        if (this.featured != this.previousFeatured) {
          this.page = 1;
        }
      }
      this.previousFeatured = this.featured;
    }
    
    params = params.append('page', this.page.toString());
    
    this.location.go(this.router.url.split("?")[0], params.toString());

    this.documentariesSubscription = this.service.getAllDocumentaries(params)
      .subscribe(
          result => {
            this.config = {
              itemsPerPage: 12,
              currentPage: this.page,
              totalItems: result['count_results']
            };
            this.documentaries = result['items'];
            console.log(result);
          }
      );
  }

  fetchVideoSources() {
    this.videoSourcesSubscription = this.videoSourceService.getAllVideoSources()
      .subscribe(result => {
        this.videoSources = <any> result;
      });
  }

  fetchCategories() {
    this.categoriesSubscription = this.categoryService.getAllCategories()
      .subscribe(result => { 
        this.categories = <any> result;
      });
  }

  pageChanged(event) {
    console.log(event);
    this.config.currentPage = event;
    this.page = event;
    this.fetchDocumentaries();
  }

  onVideoSourceSelected(value: string) {
    this.videoSource = value;
    this.fetchDocumentaries();
  }

  onCategoriesSelected(value: string) {
    this.category = value;
    this.fetchDocumentaries();
  }

  onStatusSelected(value: string) {
    this.status = value;
    this.fetchDocumentaries();
  }

  onFeaturedSelected(value: string) {
    this.featured = value;
    this.fetchDocumentaries();
  }

  ngOnDestroy() {
    this.documentariesSubscription.unsubscribe();
    this.queryParamsSubscription.unsubscribe();
    this.videoSourcesSubscription.unsubscribe();
    this.categoriesSubscription.unsubscribe();
  }
}
 

Add/Edit Standalone & Series Documentaries

Behold! A script to add/edit standalone & series documentaries plus using third party API’s such as IMDB and Youtube to auto populate data.

And here is the code. I’m relatively new to Angular (this is my second project) so I don’t know if I am doing things the right way, please leave feedback.

import { YoutubeService } from './../../../services/youtube.service';
import { UserService } from './../../../services/user.service';
import { DocumentaryService } from './../../../services/documentary.service';
import { YearService } from './../../../services/year.service';
import { CategoryService } from './../../../services/category.service';
import { HttpParams } from '@angular/common/http';
import { Documentary } from './../../../models/documentary.model';
import { FormGroup, FormControl, Validators, FormArray, FormBuilder } from '@angular/forms';
import { AngularEditorConfig } from '@kolkov/angular-editor';
import { Component, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core';
import { VideoSourceService } from 'src/app/services/video-source.service';
import { Router, ActivatedRoute } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { IMDB } from 'src/app/models/imdb.model';
import { OMDBService } from 'src/app/services/omdb.service';
import { Location } from "@angular/common";

@Component({
  selector: 'app-documentary-add',
  templateUrl: './documentary-add.component.html',
  styleUrls: ['./documentary-add.component.css']
})
export class DocumentaryAddComponent implements OnInit {
  public slug;

  public editMode = false;

  public type;

  public activeIdString;

  public documentary: Documentary;
  public categories;
  public years;
  public videoSources;
  public posterImgURL;
  public wideImgURL;
  public imdb: IMDB;
  public thumbnailImgURLDict = {};

  private queryParamsSubscription;
  private routeParamsSubscription;
  private documentaryBySlugSubscription;
  private meSubscription;
  private getByImdbIdSubscription;

  public myStandaloneDocumentaries;
  public showStandaloneDocumentaries = false;

  public showStandaloneForm:boolean = false;
  public standaloneFormLoaded = false;
  public showStandaloneAddTitleButton = true;

  public showEpisodicForm = false;
  public showEpisodicPage = false;
  public showEpisodicDocumentaries = false;
  public showEpisodicAddTitleButton = true;
  public myEpisodicDocumentaries;
  
  public isFetchingEpisodicDocumentaries = false;

  public showStandalonePage = false;
  public showSearchedDocumentariesFromIMDB = false;
  public showSearchedDocumentaryFromIMDB = false;

  public isFetchingDocumentariesFromIMDB = false;
  public isFetchingDocumentaryFromIMDB = false;

  public searchedDocumentariesFromIMDB;
  public searchedDocumentaryFromIMDB;

  public searchedVideosFromYoutube;
  public isFetchingVideosFromYoutube = true;
  public showSearchedVideosFromYoutube = false;

  public isFetchingStandaloneDocumentaries = false;
  public isFetchingYears = false;
  public isFetchingVideoSources = false;
  public isFetchingCategories = false;
  
  public hasToggledStandaloneForm = false;

  public hasToggledEpisodicForm = false;

  public submitted = false;

  public posterError = false;

  public errors;

  public seasonNumber = 1;
  public episodeNumber = 1;

  standaloneForm: FormGroup;
  episodicForm: FormGroup;
  imdbForm: FormGroup;
  youtubeForm: FormGroup;

  standaloneConfig: any;
  episodicConfig: any;
  page;
  me;

  closeResult: string;

  editorConfig: AngularEditorConfig = {
    editable: true,
    spellcheck: true,
    height: '25rem',
    minHeight: '5rem',
    placeholder: 'Enter text here...',
    translate: 'no',
    uploadUrl: 'v1/images', // if needed
  };

  constructor(
    private categoryService: CategoryService,
    private yearService: YearService,
    private videoSourceService: VideoSourceService,
    private documentaryService: DocumentaryService,
    private omdbService: OMDBService,
    private userService: UserService,
    private youtubeService: YoutubeService,
    private router: Router,
    private location: Location,
    private cd: ChangeDetectorRef,
    private modalService: NgbModal,
    private route: ActivatedRoute,
    private fb: FormBuilder
  ) { }

  ngOnInit() {
    this.start('standalone');
  }

  start(type: string = null) {
    if (type != null) {
      this.type = type;
    }

    this.reset();

    this.documentary = new Documentary();

    this.queryParamsSubscription = this.route
      .queryParams
      .subscribe(params => {
        this.page = +params['page'] || 1;

        this.routeParamsSubscription = this.route.paramMap.subscribe(params => {
          if (type == null) {
            this.type = params['params']['type'];
          }
          this.slug = params['params']['slug'];
          this.editMode = this.slug != null;

          this.activeIdString = this.type;

          if (this.editMode) {
            this.documentaryBySlugSubscription = this.documentaryService.getDocumentaryBySlug(this.slug)
              .subscribe((result:any) => {
                this.documentary = result;
                console.log(result);

                if (this.documentary.type === 'standalone') {
                  console.log(this.documentary.type);
                  this.toggleStandaloneForm();
                  this.showStandalonePage = true;
                } else if (this.documentary.type === 'episodic') {
                  this.toggleEpisodicForm();
                  this.showEpisodicPage = true;
                }
              });
          } else {
            this.meSubscription = this.userService.getMe().subscribe(me => {
              this.me = me;

              if (this.type === 'standalone') {
                console.log("fdjksjfk");
                if (!this.hasToggledStandaloneForm) {
                  this.fetchStandaloneDocumentaries();
                  this.showStandalonePage = true;
                  console.log("dfff");
                }
              } else if (this.type === 'episodic') {
                console.log('episodic');
                if (!this.hasToggledEpisodicForm) {
                  this.fetchEpisodicDocumentaries();
                  this.showEpisodicPage = true;
                }
              }
            });
          }
        });
      });
  }

  reset() {
    this.hasToggledEpisodicForm = false;
    this.hasToggledStandaloneForm = false;
    this.showStandaloneForm = false;
    this.showEpisodicForm = false;
    this.showEpisodicPage = false;
    this.showStandalonePage = false;
    this.showEpisodicAddTitleButton = false;
    this.showStandaloneAddTitleButton = false;
    this.showStandaloneDocumentaries = false;
    this.showEpisodicDocumentaries = false;
  }

  tabChange(event) {
    console.log(event);
    this.start(event.nextId);
  }

  fetchStandaloneDocumentaries() {
    if (this.editMode) {
      this.showStandaloneDocumentaries = false;
      return;
    }

    this.isFetchingStandaloneDocumentaries = true;

    let params = new HttpParams();

    params = params.append('page', this.page.toString());

    this.location.go(this.router.url.split("?")[0], params.toString());
  
    this.documentaryService.getMyStandloneDocumentaries(params, this.me.username)
      .subscribe(result => {
        this.standaloneConfig = {
          itemsPerPage: 5,
          currentPage: this.page,
          totalItems: result['count_results']
        };
        console.log("result");
        console.log(result);
        this.myStandaloneDocumentaries = result['items'];

        this.isFetchingStandaloneDocumentaries = false;
        this.showStandaloneDocumentaries = true;
        this.showStandaloneAddTitleButton = true;
      })
  }

  fetchEpisodicDocumentaries() {

    this.isFetchingEpisodicDocumentaries = true;

    let params = new HttpParams();

    params = params.append('page', this.page.toString());

    this.location.go(this.router.url.split("?")[0], params.toString());
  
    this.documentaryService.getMyEpisodicDocumentaries(params, this.me.username)
      .subscribe(result => {
        this.episodicConfig = {
          itemsPerPage: 5,
          currentPage: this.page,
          totalItems: result['count_results']
        };
        this.myEpisodicDocumentaries = result['items'];

        this.isFetchingEpisodicDocumentaries = false;
        this.showEpisodicDocumentaries = true;
        this.showEpisodicAddTitleButton = true;
      })
  }

  toggleStandaloneForm() {
    console.log("toggle");
    this.showStandaloneAddTitleButton = false;

    this.showStandaloneDocumentaries = false;

    this.showStandaloneForm = !this.showStandaloneForm;

    this.initYears();
    this.initVideoSources();
    this.initCategories();
    this.initStandaloneForm();

    this.hasToggledStandaloneForm = true;
  }

  toggleEpisodicForm() {
    console.log("toggle");
    this.showEpisodicAddTitleButton = false;

    this.showEpisodicDocumentaries = false;

    this.showEpisodicForm = !this.showEpisodicForm;

    this.initYears();
    this.initVideoSources();
    this.initCategories();
    this.initEpisodicForm();

    this.hasToggledEpisodicForm = true;
  }

  initYears() {
    this.isFetchingYears = true;

    this.years = this.yearService.getAllYearsForForm();

    this.isFetchingYears = false;
  }

  initVideoSources() {
    this.isFetchingVideoSources = true;

    let params: HttpParams;
    this.videoSourceService.getAll(params)
      .subscribe(result => {
        this.videoSources = result;
        
        this.isFetchingVideoSources = false;
      });
  }

  initCategories() {
    this.isFetchingCategories = true;

    let params: HttpParams;
    this.categoryService.getAll(params)
      .subscribe(result => {
        this.categories = result;

        this.isFetchingCategories = false;
      });
  }

  initStandaloneForm() {
    let title = this.documentary.title;
    let category = null;
    if (this.documentary.category) {
      category = this.documentary.category.id;
    }
    let storyline = this.documentary.storyline;
    let summary = this.documentary.summary;
    let videoSource = null;
    if (this.documentary.videoSource) {
      videoSource = this.documentary.videoSource.id
    }
    let videoId = this.documentary.videoId;
    let year = this.documentary.year;
    let length = this.documentary.length;
    let poster = this.documentary.poster;
    this.posterImgURL = this.documentary.poster;
    let wideImage = this.documentary.wideImage;
    this.wideImgURL = this.documentary.wideImage;
    let imdbId = this.documentary.imdbId;

    this.standaloneForm = new FormGroup({
      'title': new FormControl(title, [Validators.required]),
      'category': new FormControl(category, [Validators.required]),
      'storyline': new FormControl(storyline, [Validators.required]),
      'summary': new FormControl(summary, [Validators.required]),
      'videoSource': new FormControl(videoSource, [Validators.required]),
      'videoId': new FormControl(videoId, [Validators.required]),
      'year': new FormControl(year, [Validators.required]),
      'length': new FormControl(length, [Validators.required]),
      'poster': new FormControl(poster, [Validators.required]),
      'wideImage': new FormControl(wideImage, [Validators.required]),
      'imdbId': new FormControl(imdbId)
    });
  }

  initEpisodicForm(seasons = null) {
    let title = this.documentary.title;
    let category = null;
    if (this.documentary.category) {
      category = this.documentary.category.id;
    }
    let storyline = this.documentary.storyline;
    let summary = this.documentary.summary;
    let year = this.documentary.year;
    let poster = this.documentary.poster;
    this.posterImgURL = this.documentary.poster;
    let wideImage = this.documentary.wideImage;
    this.wideImgURL = this.documentary.wideImage;
    let imdbId = this.documentary.imdbId;

    this.episodicForm = this.fb.group({
      'title': new FormControl(title, [Validators.required]),
      'category': new FormControl(category, [Validators.required]),
      'storyline': new FormControl(storyline, [Validators.required]),
      'summary': new FormControl(summary, [Validators.required]),
      'year': new FormControl(year, [Validators.required]),
      'poster': new FormControl(poster, [Validators.required]),
      'wideImage': new FormControl(wideImage),
      'imdbId': new FormControl(imdbId),
      'seasons': this.fb.array([], Validators.required)
    });

    if (seasons != null) {
      seasons.forEach(season => {
        this.addNewSeason(season);
      });
    }
  }

  addNewSeason(season = null) {
    let number = season.number
    if (number == null) {
      number = this.seasonNumber;
    }

    let control = <FormArray>this.episodicForm.controls.seasons;
    control.push(
      this.fb.group({
        'seasonNumber': new FormControl(number, [Validators.required]),
        'episodes': this.fb.array([], Validators.required)
      })
    );

    let episodes = season.episodes;
    if (season != null && episodes != null) {
      episodes.forEach(episode => {
        let episodesControl = control.at(number - 1).get('episodes');
        this.addNewEpisode(episodesControl, season, episode);
      })
    }

    if (season == null) {
      this.seasonNumber++;
    }
  }

  deleteSeason(index) {
    var seasonsFormArray = this.episodicForm.get("seasons") as FormArray;
    seasonsFormArray.removeAt(index);
  }

  addNewEpisode(control, season = null, episode = null) {
    let episodeNumber;
    let title;
    let storyline;
    let summary;
    let year;
    let length;
    let imdbId;
    let videoId;
    let videoSource;
    let poster;

    if (episode != null) {
      episodeNumber = episode.number;
      title = episode.title;
      imdbId = episode.imdbId;
      poster = episode.thumbnail;
      summary = episode.summary;
      storyline = episode.plot;
      year = episode.year;
      videoId = episode.videoId;
      videoSource = episode.videoSource;

      let seasonNumber = season.number;
      if (this.thumbnailImgURLDict[seasonNumber - 1] == undefined) {
        this.thumbnailImgURLDict[seasonNumber - 1] = {};
      }
      this.thumbnailImgURLDict[seasonNumber - 1][episodeNumber - 1] = poster;
    }

    control.push(
      this.fb.group({
        'episodeNumber': new FormControl(episodeNumber, [Validators.required]),
        'title': new FormControl(title, [Validators.required]),
        'imdbId': new FormControl(imdbId),
        'storyline': new FormControl(storyline, [Validators.required]),
        'summary': new FormControl(summary, [Validators.required]),
        'length': new FormControl(length, [Validators.required]),
        'year': new FormControl(year, [Validators.required]),
        'videoSource': new FormControl(videoSource, [Validators.required]),
        'videoId': new FormControl(videoId, [Validators.required]),
        'poster': new FormControl(poster, [Validators.required]),
      }));
  }
  
  deleteEpisode(seasonIndex, episodeIndex) {
    var seasonsFormArray = this.episodicForm.get("seasons") as FormArray;
    var episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;
    episodesFormArray.removeAt(episodeIndex);
  }

  get fStandalone() { return this.standaloneForm.controls; }
  get fEpisodic() { return this.episodicForm.controls; }

  onPosterChange(event) {
    let reader = new FileReader();
 
    if (event.target.files && event.target.files.length) {
      const [file] = event.target.files;
      reader.readAsDataURL(file);
    
      reader.onload = () => {
        if (this.type == 'standalone') {
          this.standaloneForm.patchValue({
            poster: reader.result
          });
        } else {
          this.episodicForm.patchValue({
            poster: reader.result
          })
        }
        
        this.cd.markForCheck();

        this.posterImgURL = reader.result; 
      };
    }
  }

  onThumbnailChange(event, seasonNumber, episodeNumber) {
    console.log(event);
    let reader = new FileReader();
 
    if (event.target.files && event.target.files.length) {
      const [file] = event.target.files;
      reader.readAsDataURL(file);
    
      reader.onload = () => {
        var seasonsFormArray = this.episodicForm.get("seasons") as FormArray;
        var episodesFormArray = seasonsFormArray.at(seasonNumber).get("episodes") as FormArray;
        episodesFormArray.at(episodeNumber)['controls']['poster'].patchValue(reader.result);

        if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
          this.thumbnailImgURLDict[seasonNumber] = {};
        }
        this.thumbnailImgURLDict[seasonNumber][episodeNumber] = reader.result;
      }
        
        this.cd.markForCheck();

      };
    }

  getThumbnailForSeasonAndEpsiode(seasonNumber, episodeNumber) {
    if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
      this.thumbnailImgURLDict[seasonNumber] = {};
    }

    return this.thumbnailImgURLDict[seasonNumber][episodeNumber];
  }
  
  onWideImageChange(event) {
    let reader = new FileReader();
 
    if (event.target.files && event.target.files.length) {
      const [file] = event.target.files;
      reader.readAsDataURL(file);
    
      reader.onload = () => {
        if (this.type == 'standalone') {
          this.standaloneForm.patchValue({
            wideImage: reader.result
          });
        } else {
          this.episodicForm.patchValue({
            wideImage: reader.result
          });
        }
        
        // need to run CD since file load runs outside of zone
        this.cd.markForCheck();

        this.wideImgURL = reader.result; 
      };
    }
  }

  initIMDBFrom() {
    let title = null;

    if (this.type === 'standalone') {
      title = this.standaloneForm.value.title;
    } else if (this.type === 'episodic') {
      title = this.episodicForm.value.title;
    }

    this.imdbForm = new FormGroup({
      'title': new FormControl(title, [Validators.required])
    });

    if (title) {
      this.searchOMDB();
    }
  }

  initYoutubeForm() {
    let title = this.standaloneForm.value.title;

    this.youtubeForm = new FormGroup({
      'title': new FormControl(title, [Validators.required])
    });

    if (title) {
      this.searchYoutube();
    }
  }

  openIMDBModal(content) {
    this.initIMDBFrom();
    console.log(content);
    this.modalService.open(content, {ariaLabelledBy: 'modal-omdb'}).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`;
    });
  }

  openYoutubeModal(content) {
    this.initYoutubeForm();

    this.modalService.open(content, {ariaLabelledBy: 'modal-youtube'}).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`;
    });
  }

  imdbView(imdbId) {
    this.isFetchingDocumentaryFromIMDB = true;
    this.showSearchedDocumentariesFromIMDB = false;
    this.showSearchedDocumentaryFromIMDB = true;

    this.omdbService.getByImdbId(imdbId, 'movie')
      .subscribe((result: any) => {
        console.log(result);
        this.searchedDocumentaryFromIMDB = result;
        this.isFetchingDocumentaryFromIMDB = false;
      })
  }

  imdbSelect(selectedDocumentary) {
    this.documentary.title = selectedDocumentary.Title;

    if (this.documentary.imdbId != selectedDocumentary.imdbID) {
      this.documentary.imdbId = selectedDocumentary.imdbID;
      this.documentary.storyline = selectedDocumentary.Plot;
      this.documentary.year = selectedDocumentary.Year;
      this.documentary.poster = selectedDocumentary.Poster;
      this.posterImgURL = selectedDocumentary.Poster;
    }

    if (this.type === 'standaloine') {
      this.initStandaloneForm();
      this.modalService.dismissAll();  
    } else if (this.type === 'episodic') {
      this.getByImdbIdSubscription = this.omdbService.getByImdbId(selectedDocumentary.imdbID, this.type)
        .subscribe((result: any) => {
          console.log("result");
          console.log(result);
          let seasons = result['seasons'];
          this.initEpisodicForm(seasons);
          this.modalService.dismissAll();
      
      });
    }
  }

  youtubeSelect(selectedVideo) {
    this.modalService.dismissAll();

    if (!this.documentary.title) {
      this.documentary.title = selectedVideo.snippet.title;
    }

    if (!this.documentary.storyline) {
      this.documentary.storyline = selectedVideo.snippet.description;
    }

    if (!this.documentary.wideImage) {
      this.documentary.wideImage = selectedVideo.snippet.thumbnails.high.url;
      this.wideImgURL = selectedVideo.snippet.thumbnails.high.url;
    }

    this.documentary.videoId = selectedVideo.id.videoId;

    this.initStandaloneForm();
  }

  searchOMDB() {
    this.isFetchingDocumentariesFromIMDB = true;
    this.showSearchedDocumentaryFromIMDB = false;
    this.showSearchedDocumentariesFromIMDB = true;

    let title = this.imdbForm.value.title;
    let imdbType = 'movie';
    if (this.type === 'episodic') {
      imdbType = 'series';
    }
    this.omdbService.getSearchedDocumentaries(title, imdbType)
      .subscribe((result: any) => {
        console.log(result);
        this.searchedDocumentariesFromIMDB = result['Search'];
        this.isFetchingDocumentariesFromIMDB = false;
      });
  }

  searchYoutube() {
    this.isFetchingVideosFromYoutube = true;
    this.showSearchedVideosFromYoutube = true;

    let title = this.youtubeForm.value.title;
    this.youtubeService.getSearchedDocumentaries(title)
      .subscribe((result: any) => {
        this.searchedVideosFromYoutube = result['items'];
        this.isFetchingVideosFromYoutube = false;
      });
  }

  onStandaloneSubmit() {
    if (!this.standaloneForm.valid) {
      return;
    }

    this.submitted = true;
    this.errors = null;

    let values = this.standaloneForm.value;

    let formValue = this.standaloneForm.value;

    if (this.editMode) {
      this.documentaryService.editStandaloneDocumentary(this.documentary.id, formValue)
        .subscribe((result: any) => {
          this.reset();
          this.router.navigate(["/add/standalone"]);
        },
        (error) => {
          console.log(error);
          this.errors = error.error;
        });
    } else {
      this.documentaryService.createStandaloneDocumentary(formValue)
        .subscribe((result: any) => {
          this.reset();
          this.router.navigate(["/add/standalone"]);
      },
      (error) => {
        console.log(error);
        this.errors = error.error;
      });
    }
  }

  onEpisodicSubmit() {
    console.log(this.fEpisodic);
    console.log(this.episodicForm.value);

    if (!this.episodicForm.valid) {
      return;
    }

    this.submitted = true;
    this.errors = null;

    let values = this.episodicForm.value;

    let formValue = this.episodicForm.value;

    if (this.editMode) {
      this.documentaryService.editEpisodicDocumentary(this.documentary.id, formValue)
        .subscribe((result: any) => {
          this.reset();
          this.router.navigate(["/add/episodic"]);
        },
        (error) => {
          console.log(error);
          this.errors = error.error;
        });
    } else {
      this.documentaryService.createEpisodicDocumentary(formValue)
        .subscribe((result: any) => {
          this.reset();
          this.router.navigate(["/add/episodic"]);
      },
      (error) => {
        console.log(error);
        this.errors = error.error;
      });
    }
  }
  
  pageChanged(event) {
    console.log(event);
    this.standaloneConfig.currentPage = event;
    this.page = event;
    this.fetchStandaloneDocumentaries();
  }
  
  episodicPageChanged(event) {
    console.log(event);
    this.episodicConfig.currentPage = event;
    this.page = event;
    this.fetchEpisodicDocumentaries();
  }

  ngOnDestroy() {
    this.queryParamsSubscription.unsubscribe();
    this.routeParamsSubscription.unsubscribe();
    if (this.documentaryBySlugSubscription != null) {
      this.documentaryBySlugSubscription.unsubscribe();
    }
    if (this.meSubscription != null) {
      this.meSubscription.unsubscribe();
    }
    if (this.getByImdbIdSubscription != null) {
      this.getByImdbIdSubscription.unsubscribe();
    }
  }
}
 

Modulus Examples

aka Fizzbuzz alternatives.

getColumnsForCategories(categories) {
    let categoriesCount = 0;
    for (var key in categories) {
      categoriesCount++;
    }

    let half = Math.floor(categoriesCount / 2);
    let remainder = categoriesCount % half;

    let categoriesLeftColumnLength = half + remainder;
    let categoriesRightColumnLength = half;

    let categoriesLeftColumn = new Set();
    for (let i = 0; i < categoriesLeftColumnLength; i++) {
      categoriesLeftColumn.add(categories[i]);
    }

    let categoriesRighttColumn = new Set();
    for (let i = categoriesLeftColumnLength; i < (categoriesLeftColumnLength + categoriesRightColumnLength); i++) {
      categoriesRighttColumn.add(categories[i]);
    }

    let categoriesColumns = new Map();
    categoriesColumns.set('left', categoriesLeftColumn);
    categoriesColumns.set('right', categoriesRighttColumn);

    return categoriesColumns;
   }
convertArrayOfDocumentariesToMap(documentaries, amountPerRow, amountTotal) {
    let cardDecks = new Map();

    let counter = 0;
    for (let i in documentaries) {
        if (+i === amountTotal) {
          break;
        }

        let documentary = documentaries[i];

        let cardDeck = cardDecks.get(counter);
        if (cardDeck === undefined || !cardDeck) {
          cardDeck = new Set();
        } 

        cardDeck.add(documentary);
        cardDecks.set(counter, cardDeck);

        if (+i != 0 && (+i + 1) % amountPerRow === 0) {
          counter++;
        }
    }

    return cardDecks;
   }
 

Parent & Children Activity

private function convertActivityToArray(array $activity)
    {
        $activityArray = array();

        $previousGroupNumber = null;
        /** @var Activity $activityItem */
        foreach ($activity as $activityItem) {
            $type = $activityItem->getType();
            $groupNumber = $activityItem->getGroupNumber();
            $user = $activityItem->getUser();
            $name = $user->getName();
            $avatar = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/avatar/' . $user->getAvatar();
            $data = $activityItem->getData();
            $created = $activityItem->getCreatedAt();

            $activityArray[$groupNumber]['type'] = $type;
            $activityArray[$groupNumber]['created'] = $created;

            if ($type == ActivityType::Like) {
                if ($groupNumber != $previousGroupNumber) {
                    $data['documentaryThumbnail'] = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $data['documentaryThumbnail'];
                    $activityArray[$groupNumber]['parent']['data'] = $data;
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $data['documentaryThumbnail'] = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $data['documentaryThumbnail'];
                    $child['data'] = $data;
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            } else if ($type == ActivityType::Comment) {
                $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                $activityArray[$groupNumber]['parent']['data'] = $data;
            } else if ($type == ActivityType::Joined) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;#
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            } else if ($type == ActivityType::Added) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityArray[$groupNumber]['parent']['data'] = $data;
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $child['data'] = $data;
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            }

            $previousGroupNumber = $groupNumber;
        }

        return $activityArray;
    }