<repo_name>YIRer/angular-portfolio<file_sep>/my-portfolio/src/app/portfolio/photo/new/new.component.ts
import { Component, OnInit } from '@angular/core';
import { Router,ActivatedRoute,Params } from '@angular/router';
import { FormGroup, FormControl, Validators} from '@angular/forms';
import { PhotoService } from '../photo.service';
import { Response } from '@angular/http';
import { DataService } from '../../../shared/data.service';
// import { PhotoBrick } from '../photo.model';

@Component({
  selector: 'app-new',
  templateUrl: './new.component.html',
  styleUrls: ['./new.component.css']
})
export class NewComponent implements OnInit {
  // photos:PhotoBrick[];
  photoForm: FormGroup;
  editMode =false;
  editId : number;
  comment = [];
  photos : any;

  constructor(
    private route : ActivatedRoute,
    private router : Router,
    private photoService : PhotoService,
    private dataService : DataService) { }

  ngOnInit() {
    this.route.params
    .subscribe(
      (params:Params)=>{
        this.editId = params['id'];
        this.editMode = params['id'] != null;
        // console.log(this.editMode);
        this.initForm()
      }
    )
  }
  private initForm(){
    let title = '';
    let auth = '';
    let imgsrc = '';
    let desc = '';
    // console.log(this.photoService);
    if(this.editMode){
      const photo = this.photoService.getPhoto(this.editId);
      title = photo.name;
      auth = photo.auth;
      imgsrc = photo.imgsrc;
      desc = photo.description;
      this.comment = photo.comment;
    }
    this.photoForm = new FormGroup({
      'title': new FormControl(title, Validators.required),
      'auth': new FormControl(auth, Validators.required),
      'imgsrc': new FormControl(imgsrc, Validators.required),
      'description': new FormControl(desc, Validators.required)
    })
  }
  onSubmit(){
    let photoId = this.photoService.PhotosIds;
    if(!this.editMode){
      photoId = photoId+1;
    }
    let pushValue = {
      name:this.photoForm.value.title,
      description:this.photoForm.value.description,
      auth:this.photoForm.value.auth,
      imgsrc:this.photoForm.value.imgsrc,
      date : new Date(),
      id : photoId,
      comment:this.comment
    };
    if(this.editMode){
      // console.log("edit!")
      this.photoService.onUpdate(pushValue.id, pushValue);
      this.photos = this.photoService.getPhotos()
      this.dataService.updata();
    }else{
      this.photoService.addBrick(pushValue);
      this.photos = this.photoService.getPhotos()
      this.dataService.updata();
      // this.dataService.storagePhotos();
      // this.photoService.setPhotos(this.photos);
    }
    this.onCancle()
  }
  onCancle(){
    this.router.navigate(['../'],{relativeTo:this.route});
  }
  updata(){
    this.dataService.storagePhotos()
    .subscribe((response:Response)=>{
      // console.log(response);
    })
  }
}
<file_sep>/my-portfolio/src/app/main/contact/contact.component.ts
import { Component, OnInit} from '@angular/core';
declare var jQuery:any;
declare var $:any;
@Component({
  selector: 'app-contact',
  templateUrl: './contact.component.html',
  styleUrls: ['./contact.component.css']
})
export class ContactComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    let addTooltip = $('address').length;
    for(let i = 0 ; i < addTooltip; i++){
      $('address').eq(i).tooltip({trigger:'hover'});
    }
  }
  clickText(value){
    let innerText = value;
    $('#clip_target').val(innerText);
    $('#clip_target').select();
    try {
      let successful = document.execCommand('copy');
    }
    catch (err) {
    }
  }
}
<file_sep>/my-portfolio/src/app/portfolio/photo/photo.model.ts
import { PhotoComment } from './photoComment/photocomment.model';

export class PhotoBrick {
  public name:string;
  public description:string;
  public auth:string;
  public imgsrc:string
  public date : any;
  public id : any
  public comment : PhotoComment[];
  constructor(name:string, desc:string, auth:string, imgsrc:string, date : any, id:any, comment:PhotoComment[]){
    this.name = name;
    this.description = desc;
    this.auth = auth;
    this.imgsrc = imgsrc;
    this.date = date;
    this.id = id;
    this.comment = comment;
  }
}
<file_sep>/my-portfolio/src/app/main/profile/show-profile/show-profile.component.ts
import {
  Component,
  OnInit,
  HostListener,
  OnDestroy
} from '@angular/core';
import {Http} from '@angular/http';
import * as firebase from 'firebase';
import { Subject } from 'rxjs/Subject';

declare var jQuery:any;
declare var $:any;

@Component({
  selector: 'app-show-profile',
  templateUrl: './show-profile.component.html',
  styleUrls: ['./show-profile.component.css']
})
export class ShowProfileComponent implements OnInit, OnDestroy{
  profile:string
  data:any;
  profileImg = new Subject<boolean>();
  techImgSum = 0;
  techImgLoaded = new Subject<boolean>();
  endLoading = new Subject;
  oneStep = false;
  twoStep = false;

  ngOnInit() {
    // 로딩 체크
    this.profileImg.subscribe((value:boolean)=>{
      if(value === true){
        this.oneStep = true;
        this.endLoading.next();
      }
    });
    this.techImgLoaded.subscribe((value:boolean)=>{
      if(value === true){
        this.twoStep = true;
        this.endLoading.next();
      }
    });
    // 로딩완료시 에니메이션 표시
    this.endLoading.subscribe(()=>{
      if(this.oneStep === true && this.twoStep ===true){
        $('#loading').addClass("hide");
        $("#profileMain").removeClass("hide");
        $("#introduce").removeClass("hide");
        $('#profileImg').addClass("animated fadeIn");
        for(let i = 0; i < this.data.length; i++){
          $('.techImg').eq(i).addClass("animated rotateIn")
        }
      }
    })
  }

  // 로딩 완료 시 체크
  profileLoad(){
    $('#profileImg').removeClass("hide");
    this.profileImg.next(true);
  }
  techLoad(){
    this.techImgSum++;
    if(this.techImgSum === this.data.length){
      $('#tech').removeClass("hide");
      for(let i = 0 ; i < this.techImgSum; i++){
        $('.techImg').eq(i).tooltip({trigger:'hover'});
      }
      this.techImgLoaded.next(true);
    }

  }
  constructor(
      private http : Http) {
        // 데이터 로드 및 도큐맨트 옵션사용
    this.http.get('https://portfolio-project-768d9.firebaseio.com/logo.json')
    .subscribe(data => this.data = data.json(),
                err => console.log(err),
                () =>{
                  const profile = firebase.storage().ref().child('KakaoTalk_20161008_165029271.jpg');
                  profile.getDownloadURL().then(url => this.profile = url);
                  for(let i = 0; i <this.data.length ; i++){
                    firebase.storage().ref().child("logo/"+this.data[i].name+".png")
                    .getDownloadURL().then(url => this.data[i].imgUrl = url);
                  }
                });
  }
  // 스크롤 이벤트 체크
  @HostListener("scroll", ['$event'])
  // 스크롤시 발생할 이벤트
  onWindowScroll($event) {
     let number = $event.target.scrollTop;
     let introduce = $('#introduce').offset().top;
     if(number > introduce){
       $('#introduce').removeClass('animated fadeOut');
       $('#introduce').addClass('animated fadeIn');
     }else{
      $('#introduce').removeClass('animated fadeIn');
      $('#introduce').addClass('animated fadeOut');
     }
   }
  //  메모리 누수 방지
  ngOnDestroy(){
    this.profileImg.unsubscribe();
    this.techImgLoaded.unsubscribe();
    this.endLoading.unsubscribe()
  }
}
<file_sep>/my-portfolio/src/app/main/main.component.ts
import { Component, OnInit, ViewChild, ElementRef} from '@angular/core';

@Component({
  selector: 'app-main',
  templateUrl: './main.component.html',
  styleUrls: ['./main.component.css']
})
export class MainComponent implements OnInit {
isClassVisible = false;
  @ViewChild('slide') slide:ElementRef
  constructor() {
 }

  classChanged(name:string){
    this.slide.nativeElement.className = 'animated bounceOutDown';
    setTimeout(()=>{
      this.isClassVisible = true;
    },1000)
  }

  ngOnInit() {
    this.isClassVisible = false;
  }
}
<file_sep>/my-portfolio/src/app/portfolio/photo/detail/detail.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute, Params } from '@angular/router';
import { PhotoBrick } from '../photo.model';
import { PhotoService } from '../photo.service';
import { Response } from '@angular/http';
import { DataService } from '../../../shared/data.service';

@Component({
  selector: 'app-detail',
  templateUrl: './detail.component.html',
  styleUrls: ['./detail.component.css']
})
export class DetailComponent implements OnInit {
  photo:PhotoBrick;
  id:any;
  constructor(private route:ActivatedRoute,
              private router:Router,
              private photoService: PhotoService,
              private dataService : DataService) { }

  ngOnInit() {
    this.route.params
    .subscribe(
      (params:Params)=>{

        this.id = params['id']
        if(this.photoService.onCheckId(this.id)){
            this.photo = this.photoService.getPhoto(this.id);
        }
      }
    )
  }
  onCancle(){
    this.router.navigate(['portfolio/photo']);
  }
  onDelete(){
    this.photoService.onDelete(this.id);
    // this.photos = this.photoService.getPhotos()
    this.dataService.updata();
    this.router.navigate(['portfolio/photo']);
  }
  onEditPhoto(){
    this.router.navigate(['edit'],{relativeTo:this.route});
  }
  updata(){
    this.dataService.storagePhotos()
    .subscribe((response:Response)=>{
    })
  }
}
<file_sep>/my-portfolio/src/app/portfolio/photo/photo-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PhotoComponent } from './photo.component';
import { NewComponent } from './new/new.component';
import { PhotoItemComponent } from './photoItem/photoItem.component';
import { DetailComponent } from './detail/detail.component';

const photoRoutes: Routes = [
  { path: '', component: PhotoComponent},
  { path: 'new', component: NewComponent},
  { path: ':id', component: DetailComponent },
  { path: ':id/edit', component: NewComponent}
];

@NgModule({
  imports: [
    RouterModule.forChild(photoRoutes)
  ],
  exports: [RouterModule],
  providers:[
  ]
})
export class PhotoRoutingModule {}
<file_sep>/my-portfolio/src/app/portfolio/photo/photoComment/photoComment.component.ts
import { Component, OnInit,Input } from '@angular/core';
import { FormGroup, FormControl, Validators} from '@angular/forms';
import { PhotoBrick } from '../photo.model';
import { PhotoService } from '../photo.service'
import { PhotoComment } from './photocomment.model'
import { Subject } from "rxjs/Subject";
import { Response } from '@angular/http';
import { DataService } from '../../../shared/data.service';
import {
  trigger,
  state,
  style,
  transition,
  animate,
  keyframes,
  group
} from '@angular/animations';

declare var $;

@Component({
  selector: 'app-photo-comment',
  templateUrl: './photoComment.component.html',
  styleUrls: ['./photoComment.component.css'],
  animations: [
    trigger('list1', [
      state('in', style({
        opacity: 1,
        transform: 'translateX(0)'
      })),
      transition('void => *', [
        style({
          opacity: 0,
          transform: 'translateX(-100px)'
        }),
        animate(300)
      ]),
      transition('* => void', [
        animate(300, style({
          transform: 'translateX(100px)',
          opacity: 0
        }))
      ])
    ])
  ]
})
export class PhotoCommentComponent implements OnInit {
  @Input() photo : PhotoBrick;
  commentForm:FormGroup;

  constructor(
              private photoService : PhotoService,
              private dataService : DataService) {}

  ngOnInit() {
    this.cForm();
  }

  private cForm(){
    let auth ='';
    let comment ='';
    this.commentForm = new FormGroup({
      auth: new FormControl(auth, Validators.required),
      comment: new FormControl(comment, Validators.required),
    })
  }
  onAddComment(){
    let date= new Date();
    let pushValue = new PhotoComment(
      this.commentForm.controls.auth.value,
      this.commentForm.controls.comment.value,
      date
    )
    this.photo.comment.push(pushValue);
    this.photoService.onUpdate(this.photo.id, this.photo);
    this.photoService.getPhotos()
    this.dataService.updata();
    this.commentForm.reset();
  }
  onClear(){
    this.commentForm.reset();

  }
  deleteComment(i:number){
    let dd =[];
    for(let j = 0; j < this.photo.comment.length; j++){
      if(j != i){
        dd.push(this.photo.comment[j]);
      }
    }
    this.photo.comment = dd;
    this.photoService.onUpdate(this.photo.id, this.photo);
    this.photoService.getPhotos()
    this.dataService.updata();
  }
  commnetCtrl(comment:any, index:number){
    let selecComment = $('.comment').eq(index);
    let showTa = $('.editComment').eq(index);
    let changeValue = showTa[0].value;

    selecComment.toggleClass("hide");
    showTa.toggleClass("hide");
    showTa.toggleClass("pulse");

    if(showTa.hasClass("hide")){
      if(changeValue.length >0){
        comment.comment = changeValue;
        this.photo.comment[index] = comment;
        this.photoService.onUpdate(this.photo.id, this.photo);
        this.photoService.getPhotos()
        this.dataService.updata();
      }
    }else{
      showTa[0].value = comment.comment;
      setTimeout(()=>{
          showTa.focus();
      },100)
      $(showTa).tooltip({trigger:"focus"});
    }
  }

}
<file_sep>/my-portfolio/src/app/portfolio/photo/photoItem/photoItem.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { PhotoBrick } from '../photo.model';
import { Subject } from 'rxjs/Subject';
import { PhotoService } from '../photo.service';
declare var jQuery:any;
declare var $:any;

@Component({
  selector: 'app-photoItem',
  templateUrl: './photoItem.component.html',
  styleUrls: ['./photoItem.component.css']
})
export class PhotoItemComponent implements OnInit {
  @Input() photo:any;
  @Input() index:number;
  @Input() photoId:any;
  constructor(private photoService: PhotoService){}
  ngOnInit() {}
  imageLoad(){
    this.photoService.itemLoaded.next(true);
    setTimeout(()=>{
      $('.card').eq(this.index).addClass("animated fadeIn");
    },500);
  }

}
<file_sep>/my-portfolio/src/app/shared/data.service.ts
import { Injectable } from '@angular/core';
import { Http } from '@angular/http';
import { PhotoService } from '../portfolio/photo/photo.service';
import { PhotoBrick } from '../portfolio/photo/photo.model';
import { Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';

@Injectable()

export class DataService{
  constructor(private http : Http, private photoService : PhotoService){}
  storagePhotos(){
    return this.http.put('https://portfolio-project-768d9.firebaseio.com/photos.json'
                          , this.photoService.getPhotos());
  }
  getPhotos(){
    setTimeout(()=>{
    this.http.get('https://portfolio-project-768d9.firebaseio.com/photos.json')
            .map(
              (response:Response)=>{
                const photos: PhotoBrick[] = response.json();
                let aa =[];
                if(photos != null){
                  for (let i =0 ;i< photos.length; i++){
                    if(!photos[i].comment){

                      photos[i].comment = [];
                    }
                    let ss = new PhotoBrick(
                      photos[i].name,
                      photos[i].description,
                      photos[i].auth,
                      photos[i].imgsrc,
                      photos[i].date,
                      photos[i].id,
                      photos[i].comment
                    )
                    aa.push(ss);
                  }
                }

                return aa;
              }
            )
          .subscribe((photos:PhotoBrick[])=>{
                this.photoService.setPhotos(photos);

          });
        },1000)
  }
  updata(){
    this.storagePhotos()
    .subscribe((response:Response)=>{
    })
  }
}
<file_sep>/my-portfolio/src/app/portfolio/photo/photo.component.ts
import { Component, OnInit, OnDestroy} from '@angular/core';
import { Router,ActivatedRoute } from '@angular/router';
import { PhotoBrick } from './photo.model';
import { PhotoService} from './photo.service';
import { Subscription} from 'rxjs/Subscription';
import { Response } from '@angular/http';
import { DataService } from '../../shared/data.service';
import {Observable} from 'rxjs/Observable';

declare var jQuery:any;
declare var $:any;

@Component({
  selector: 'app-photo',
  templateUrl: './photo.component.html',
  styleUrls: ['./photo.component.css']
})
export class PhotoComponent implements OnInit, OnDestroy {

  photos : any;
  checkLength : number = 0;
  subscription : Subscription;
  imageLoaded :Subscription;
  imageCheck  = 0;
  constructor(private photoService : PhotoService,
                private router: Router,
                private route: ActivatedRoute,
                private dataService : DataService) { }

  ngOnInit() {
    this.subscription = this.photoService.photoListChanged
    .subscribe((photos:PhotoBrick[])=>{
          this.photos = photos;
          this.checkLength = this.photos.length;
  })
    this.imageLoaded =  this.photoService.itemLoaded
    .subscribe((value:boolean)=>{
      if(value === true){
        this.imageCheck++

        if(this.imageCheck === this.photos.length){
            setTimeout(()=>{
              $('#loading').addClass("hide");
              $('#gallery-box').removeClass("hide");
            },500)
        }
      }
    })
    this.dataService.getPhotos();
    this.photos = this.photoService.getPhotos()
    this.checkLength = this.photos.length;
    }
  ngOnDestroy(){
    this.subscription.unsubscribe();
    this.imageLoaded.unsubscribe();
  }

}
<file_sep>/my-portfolio/src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

import { MainComponent } from './main/main.component';
import { ProfileComponent } from './main/profile/profile.component';
import { ContactComponent } from './main/contact/contact.component';

const appRoutes : Routes =[
  {
    path:'',
    component: MainComponent
  },{
    path:'profile',
    component: ProfileComponent
  },{
    path:'contact',
    component: ContactComponent
  },
  {
    path:'portfolio',
    loadChildren:'./portfolio/portfolio.module#PortfolioModule'
  },{
    path:'**',
    redirectTo: ''
  }

]

@NgModule({
  imports:[
    RouterModule.forRoot(appRoutes)
  ],
  exports:[
    RouterModule
  ]
})
export class AppRoutingModule{}
<file_sep>/my-portfolio/src/app/portfolio/photo/photo.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs/Subject';
import { Router } from '@angular/router';

import { PhotoComment } from './photoComment/photocomment.model'
import { PhotoBrick } from './photo.model';

@Injectable()
export class PhotoService{
  itemLoaded =  new Subject<boolean>();
  photoListChanged = new Subject<PhotoBrick[]>();
  private photos : PhotoBrick[] = [
    // new PhotoBrick(
    //       'A test recipe',
    //       'This is simply a test',
    //       'Yellow Man',
    //       "https://farm3.staticflickr.com/2703/4211984650_a0d58d5496.jpg",
    //       new Date(),
    //       '1',
    //       []
    //     ),
    //     new PhotoBrick(
    //       'asd',
    //       'qwer',
    //       'zxcv',
    //       "https://farm6.staticflickr.com/5055/5398254250_0a691d9804.jpg",
    //       new Date(),
    //       '2',
    //       [
    //         new PhotoComment('qwer','asdsa', new Date())
    //       ]
    //     ),
    //     new PhotoBrick(
    //       'asd',
    //       'qwer',
    //       'zxcv',
    //       "https://farm1.staticflickr.com/139/357015070_0f007ee28f.jpg",
    //       new Date(),
    //       '3',
    //       []
    //     ),
    //     new PhotoBrick(
    //           'A test recipe',
    //           'This is simply a test',
    //           'Yellow Man',
    //           "https://farm8.staticflickr.com/7596/17094319752_7de98d3e50.jpg",
    //           new Date(),
    //           '4',
    //           []
    //         )
  ];
  public PhotosIds = this.photos.length;

  setPhotos(photos:PhotoBrick[]){
    this.photos = photos;
    this.PhotosIds = this.photos.length;
    this.photoListChanged.next(this.photos);
  }

  getPhotos(){
    this.PhotosIds = this.photos.length;
    return this.photos;
  }
  addBrick(photo:PhotoBrick){
    if(this.photos.length > 0){
      this.photos.unshift(photo);
    }else{
      this.photos.push(photo);
    }
    this.PhotosIds++;
    this.photoListChanged.next(this.photos.slice());
  }
  getPhoto(index:any){
    for(let i = 0 ; i < this.photos.length; i++){
      if(this.photos[i].id == index){
        return this.photos[i];
      }
    }
  }
  onDelete(index:any){
    for(let i = 0 ; i < this.photos.length; i++){
      if(this.photos[i].id == index){
        this.photos.splice(i,1);
        this.photoListChanged.next(this.photos.slice());
      }
    }
  }
  onCheckId(id:any){
    for(let i = 0 ; i < this.photos.length; i++){
      if(this.photos[i].id == id){
        return true;
      }
    }
    return false;
  }
  onUpdate(id:any, editPhoto:PhotoBrick){
    for(let i = 0 ; i < this.photos.length; i++){
      if(this.photos[i].id == id){
        this.photos[i] = editPhoto;
        this.photoListChanged.next(this.photos.slice());
      }
    }
  }
}
<file_sep>/my-portfolio/src/app/portfolio/photo/photoComment/photoComment.model.ts
export class PhotoComment{
  constructor(
    private auth:string,
    private comment:string,
    private date:any
  ){}
}
<file_sep>/my-portfolio/README.md
포트폴리오 사이트 제작 프로젝트
===============================
Angular2를 이용한 개인포트폴리오 사이트
---------------------------------

## 1.제작이유

개인 포트폴리오 사이트를 닷홈을 통해서 배포중이 었는데, 무료호스팅 기간이 만료되어 새로운 호스팅을 구해야 했고, 최근 관심을 가지고 공부한 Angular2를 이용하여 SPA를 만들어보고 싶은 욕구가 생겨서 작업을 하게 되었다.

## 2. 제작기간 및 관련 정보

<pre>
총 제작 기간 : 14일(2017.06.12 ~ 2017.06.25)
제작인원 : 1명(본인)
작업도구 : Angular2, Javascript, Node.js, JQuery, Bootstrap, Git, Firebase
</pre>

[호스팅 링크] (https://portfolio-project-768d9.firebaseapp.com)

### 로컬에서의 실행

1. node.js환경에서 포트폴리오 폴더(my-portfolio)로 이동한다
2. npm install을 입력하여 노드 모듈 설치
3. 설치 후 ng server로 로컬에서 실행. port 4200
## 3.제작과정

전체 개발 과정은 아래와 같이 진행되었고, 여러가지 시도를 하면서 Firebase를 이용하여 배포하는 것으로 결정 되었다.

1. 사이트에 필요한 컨텐츠 정리(계획서.html), 이전에 사용되었던 컨텐츠 중에 쓸모 있는 부분은 재활용(카페베네 카피사이트)
2. 계획서를 작성하여 RESTful하게 URI를 구성하도록 계획
3. Firebase를 이용한 간단한 데이터베이스 사용 및 호스팅

### 컴포넌트 구조

* app.component
* app.module
* app-routing.module
    + header
        - header.component
    + footer
        - footer.component
    + main    
        - main.component
            - contact.component
            - profile.component
                - show-profile.component
            - start.component
    + portfolio
        - portfolio.component
            - photo.component
                - photoComponent.component
                - photoItem.component
                - new.component
                - detail.component
    + shared
        - data.service
        - dropdown-directive.directive
        - shared.module
        - shorten.pipe

### 컴포너트의 구성

<pre>
app.component : 기본이 되는 컴포넌트. firebase에서 실행
app-routing.module : 페이지 접근시 보여줄 컴포넌트 및 자식 라우트 설정
<hr>
header.component : 네비게이션등이 포함된 컴포넌트
footer.component : 화면 하단을 차지하는 컴포넌트
<hr>
<b>main</b> : 메인, 프로필, 자기소개와 관련된 컴포넌트들로 구성

main.component : 처음 접근시 샐행되는 컴포넌트
start.component : main에서 들어가기 버튼을 누르면 나타나는 페이지
profile.component : show-profile 컴포넌트 실행
show-profile.component : 동적으로 구현된 자기소개 페이지
contact.component : 연락처 페이지
shared : 공통으로 사용되는 서비스등을 모아둔 곳
data.service : 파이어베이스를 이용한 데이터 처리와 관련된 서비스
dropdown-directive.directive : 드롭다운 기능을 위한 지시자
shared.module : 드롭다운 기능을 사용하기 위한 모듈 설정
shorten.pipe : 글씨를 줄여주는 필터 설정을 위한 pipe
<hr>
<b>portfolio</b> : 포트폴리오 관련 컴포넌트 들을 모아둔 곳
portfolio.module :  포트폴리오에 관련된 모듈들을 정리
portfolio-routing.module : 포트폴리오 라우팅 처리
portfolio.component : 포트폴리오의 기본 컴포넌트
<hr>
<b>photo</b> : 이미지 갤러리
photo.component : 이미지 갤러리 기본 컴포넌트
photo-routing.module : 이미지 갤러리 라우팅 구현
photo.module : 이미지 업데이트 데이터 형태 설정
photoItem.component : 갤러리 리스트 관련 컴포넌트
photoComponent.component : 갤러리 게시글 내부 컨텐츠와 덧글에 관련된 컴포넌트
new.component : 새로운 이미지 게시 및 수정과 관련된 컴포넌트
detail.component : 게시글 내부 컨텐츠와 덧글에 관련된 컴포넌트
</pre>

## 4.제작시 어려웠던점

제작하면서 가장 힘들었던 부분은 Angular2를 이용한 것이 가장 힘들었는데, SPA를 구현하기 위해 기존의 view 부분에서 사용했던 HTML,CSS,Javscript에 관한 지식뿐만 아니라 템플릿에 대한 이해와, Angular2에서 구현한 컴포넌트 중심의 프로그램 구성에 대한 이해가 제대로 되어야 했다는 점이다.

기존의 홈페이지 제작과는 다른 컴포넌트 중심의 애플리케이션 구현과 그와 관련된 이벤트의 처리, 프론트단에서 처리해야하는 애니메이션등 처음 작업하면서 구현하기 어려운 부분이 많았고, Javascript와 비슷하지만 다른 Typescript도 걸림돌이 되기도 했다.

## 5.제작시 배웠던 점

Angular2의 이벤트 처리에 관해서 좀 더 자세하게 알게 되었고, 완벽하지 않지만, 데이터의 처리나 애니메이션을 구현하는 방법을 알게 되었다. 또 기존 Javascript 라이브러리를 Typescript 내에서 사용하는 방법을 알게 되었다. 파이어베이스를 이용한 데이터베이스 및 호스팅 경험은 다른 프로젝트에서도 사용을 고려해 볼 정도로 좋은 서비스임을 알게 되었다.<file_sep>/my-portfolio/src/app/main/start/start.component.ts
import { Component, OnInit} from '@angular/core';
import { Subject } from 'rxjs/Subject';
import * as firebase from 'firebase';
declare var jQuery:any;
declare var $:any;

@Component({
  selector: 'app-start',
  templateUrl: './start.component.html',
  styleUrls: ['./start.component.css']
})
export class StartComponent implements OnInit {
  coffee:string;
  album:string;
  loadedCheck = new Subject();
  coffeeLoaded = new Subject<boolean>();
  albumLoaded = new Subject<boolean>();
  imgLoad1 =false;
  imgLoad2 =false;

  constructor() {
    const coffee = firebase.storage().ref().child('coffee.png');
    const album = firebase.storage().ref().child('photo.png');
    coffee.getDownloadURL().then(url => this.coffee = url);
    album.getDownloadURL().then(url => this.album = url);


  }

  ngOnInit() {
    this.albumLoaded.subscribe((value:boolean)=>{
      if(value ===  true){
        this.imgLoad1 =true;
        this.loadedCheck.next();
      }
    });
    this.coffeeLoaded.subscribe((value:boolean)=>{
      if(value ===  true){
        this.imgLoad2 =true;
        this.loadedCheck.next();
      }
    });
    this.loadedCheck.subscribe(()=>{
      if(this.imgLoad1 === true && this.imgLoad2 === true){
        setTimeout(()=>{
          $('#loading').addClass("hide");
          $('#fadeShow').removeClass("hide");
          $('#fadeShow').addClass("animated fadeInUp");
        },500)
      }
    })
  }
  albumLoad(){
    this.albumLoaded.next(true);
  }
  coffeeLoad(){
    this.coffeeLoaded.next(true);
  }
  openSite(){
     window.open('http://pf.i-make.kr:8080/caffebene/html/main/main.jsp', '_blank');
  }
}
<file_sep>/my-portfolio/src/app/portfolio/portfolio-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PortfolioComponent } from './portfolio.component';

const portfolioRoutes: Routes = [
  { path: '', component: PortfolioComponent},
  { path: 'photo',
    loadChildren:'./photo/photo.module#PhotoModule' }
];

@NgModule({
  imports: [
    RouterModule.forChild(portfolioRoutes)
  ],
  exports: [RouterModule],
  providers:[
  ]
})
export class PortfolioRoutingModule {}
<file_sep>/my-portfolio/src/app/portfolio/photo/photo.module.ts
import { NgModule } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

import { PhotoComponent } from './photo.component';
import { NewComponent } from './new/new.component';
import { PhotoItemComponent } from './photoItem/photoItem.component';
import { DetailComponent } from './detail/detail.component';
import { PhotoCommentComponent } from './photoComment/photoComment.component';

import {PhotoRoutingModule} from './photo-routing.module';

@NgModule({
  declarations: [
    PhotoComponent,
    NewComponent,
    DetailComponent,
    PhotoItemComponent,
    PhotoCommentComponent

  ],
  imports: [
    CommonModule,
    ReactiveFormsModule,
    PhotoRoutingModule
  ]
})
export class PhotoModule {}
