Во второй части мы займемся созданием возможности сохранения рисунка, добавим возможность редактирования наших сохраненных рисунков, а также добавим возможность просмотра пользователем примеров работ, по которым при желании можно будет создавать такие же или лучше. Первую часть смотрим здесь. Для начала создадим интерфейс наших данных, которые мы будем использовать при взаимодействии с базой данных.

ng g i shared/interfaces/canvasPostnterface.interface.ts и поместим в него:

export interface CanvasPostInterface {
  _id?: string,
  name: string;
  image: string;
  canvas: string | [];
}

Далее создадим сервис,  реализующий классический CRUD, но перед этим создадим два вспомогательных элемента, а именно Базовый сервис от которого мы будем наследовать наш сервис и абстрактный класс методы которого мы будем реализовывать в нашем сервисе. Создаем наш родительский сервис ng g s shared/services/root со следующим кодом:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from 'src/environments/environment.dev';

@Injectable({
  providedIn: 'root'
})
export class RootService {

  protected apiUrl = с.apiUrl;

  constructor(
    protected http: HttpClient,
  ) { }

}

Не Забываем настроить environment, добавляем в environment/environment.dev.ts

apiUrl: 'localhost/api'

Создаем абстрактный класс в shared/abstract/abstract-base-service.abstarct.ts

import { Observable } from 'rxjs';

export abstract class AbstractBaseService {
 public abstract getData(): Observable<any>;
 public abstract getDataById(): Observable<any>;
 public abstract update(): Observable<any>;
 public abstract create(): Observable<any>;
 public abstract destroy(): Observable<Response>;
}

Теперь создаем сервис canvas-post в котором мы должны реализовать все абстрактные методы и в котором нам будут доступны все свойства базового класса ng g s shared/services/canvas-post, пишем в canvas-post следующий код:

import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AbstractBaseService } from '../abstract/abstract-base-service.abstarct';
import { CanvasPostInterface } from '../interfaces/canvas-post.interface';
import { RootService } from './root.service';

@Injectable({
  providedIn: 'root'
})
export class CanvasPostService extends RootService implements AbstractBaseService {
  httpOptions = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json',
    }),
  };
  protected componentUrl = `${this.apiUrl}/Canvas`;

  getData(): Observable<CanvasPostInterface[]> {
    return this.http.get<CanvasPostInterface[]>(`${this.componentUrl}/canvas`);
  }

  getDataById(id: string): Observable<CanvasPostInterface> {
    return this.http.get<CanvasPostInterface>(`${this.componentUrl}/${id}`);
  }

  update(data: CanvasPostInterface): Observable<CanvasPostInterface> {
    const body = { data };
    return this.http.patch<CanvasPostInterface>(`${this.componentUrl}/${data._id}`, body);
  }

  create(data: CanvasPostInterface): Observable<CanvasPostInterface> {
    return this.http.post<CanvasPostInterface>(`${this.componentUrl}/create`, data,
    this.httpOptions);
  }
  
  destroy(id: number): Observable<Response> {
    return this.http.delete<any>(`${this.componentUrl}/${id}`);
  }
}

В дальнейшем у нас получится вот такая структура приложения:

После наших предыдущих приготовлений мы наконец-то реализуем возможность сохранения в базу данных наших рисунков по нажатию на кнопку “Сохранить в галерею”, для этого нам необходим создать обмен данными между компонентами, редактируем в main-canvas.component.html следующую строчку:

<app-canvas 
   [uploadSuccess]="uploadSuccess"  
   (pagesEventSave)="getSaveCanvas($event)"
>
</app-canvas>

после того как мы добавили

(pagesEventSave)="getSaveCanvas($event)"

в main-canvas.component.ts добавляем метод getSaveCanvas() где мы вызываем метод create нашего сервиса CanvasPostService отвечаючего за сохранение в базу данных, в дальнейшем мы к нему вернемся, а пока он выглядит, так:

constructor(private canvasPostService: CanvasPostService) { }

 getSaveCanvas(data: {}) {

   this.canvasPostService.create(data as CanvasPostInterface)
     .subscribe(respons=>{


   })

 }

в canvas.component.html добавляем кнопку Сохранить в галерею, после чего он он должен выглядеть, вот так:

<div class="w3-card-4">
 <div class="w3-container w3-center">
   <label for="colorWell"></label>
   
   <input list="" id="colorWell" type="color" name="colorRect" 
          [(ngModel)]="colorRect">

   <button (click)="create()">Востановить рисунок</button>
   <button (click)="clearPixel()">Задать пикселю цвет фона</button>
   <button (click)="save()">Сохранить в галерею</button>

 </div>
</div>

<div class="w3-card-4 layer">
 <div class="w3-container w3-center">
   
   <canvas id="canvas" #canvas></canvas>
   
 </div>
</div>

в canvas.component.ts создаем метод save где с помощью метода pagesEventSave мы передаем данные в метод getSaveCanvas() компонента main-canvas в котором вызывается сервис сохранения данных в базу, после чего его код выглядит вот так:

@Output() pagesEventSave: EventEmitter<{}>=new EventEmitter<{}>();
//добавляем переменную name
name: string;
//в метод onResize() добавляем this.name = data.name;
  onResize(data: any) {
    this.name = data.name;
    this.innerWidth = data.innerWidth;
    this.innerHeight = data.innerHeight;
    this.widthRect = data.widthRect;
    this.heightRect = data.heightRect;
    this.numberOf = data.numberOf;
    this.borderRow = data.borderRow;
    this.numberRow = data.numberRow;
    this.canvas.width = this.innerWidth;
    this.canvas.height = this.innerHeight
    this.colorfillStyle = data.colorfillStyle;
    this.cleardraw()
    this.draw()
  }
//создаем
 save() {
   const data = this.canvas?.toDataURL();
   this.pagesEventSave.emit({image: data, name: this.name, 
                             canvas: this.matr});
 }

не забываем импортировать в app.module.ts модуль HttpClientModule

Сейчас если мы попробуем  отправить данные мы получим ошибку, для избежания этого необходимо сериализовать наш массив matr, для этого используем JSON.stringify() 

this.pagesEventSave.emit({ image: data, name: this.name, canvas: JSON.stringify(this.matr) });

После того как данные сериализованы, мы можем отправлять их на сервер.

Добавляем слайдер.

Для этого создаем компонент ng g c canvas/components/slider 

Создаем в каталоге assets каталог images, скачиваем туда картинки

Добавляем в slider.components.html

<div class="w3-content w3-display-container">

 <img class="mySlides" src="assets/images/1.webp" style="width:400px">
 <img class="mySlides" src="assets/images/2.webp" style="width:400px">
 <img class="mySlides" src="assets/images/3.webp" style="width:400px">
 <img class="mySlides" src="assets/images/4.webp" style="width:400px">
 
 <button class="w3-button w3-black w3-display-left" (click)="plusDivs(-1)">
   &#10094;
  </button>
 <button class="w3-button w3-black w3-display-right" (click)="plusDivs(1)">
   &#10095;
  </button>

</div>

 Добавляем в slider.components.ts

 

import { Component, AfterViewInit } from '@angular/core';
 
@Component({
 selector: 'app-slider',
 templateUrl: './slider.component.html',
 styleUrls: ['./slider.component.scss']
})
export class SliderComponent implements AfterViewInit {
 
 slideIndex = 1;
 constructor(private elementRef: ElementRef) { }
 
 ngAfterViewInit(): void {
   this.showDivs(this.slideIndex);
 }
 
 plusDivs(n: number) {
   this.showDivs(this.slideIndex += n);
 }
 
 showDivs(n: number) {
   console.log(n)
   let i;
   let x = document.getElementsByClassName("mySlides");
   if (n > x.length) {
     this.slideIndex = 1
   }
   if (n < 1) { this.slideIndex = x.length }
   for (i = 0; i < x.length; i++) {
     (x[i] as HTMLElement).style.display = "none";
   }
   (x[this.slideIndex - 1] as HTMLElement).style.display = "block";
 }
}
 
/*метод showDivs(n: number) можно реализовать несколькими способами
с помощью getElementsByClassName или с помощью @ElementRef 
Напишем новый метод showDivsRef(n: number) где мы будем использовать 
ElementRef
перед этим внедрим зависимость
 constructor(private elementRef: ElementRef) { } 
*/

  showDivsRef(n: number) {
    // использованием document.getElementsByClassName
    let i;
    let element = this.elementRef.nativeElement.getElementsByClassName("mySlides");
    console.log(n, element)
    if (n > element.length) {
      this.slideIndex = 1
    }

    if (n < 1) { this.slideIndex = element.length }
    if (element.length) {
      for (i = 0; i < element.length; i++) {
        element[i].style.display = "none";
      }
      element[this.slideIndex - 1].style.display = "block";
    }
  }


Добавляем в left-panel.component.html после <div class="w3-third">

<div class="w3-white w3-text-grey w3-card-4">
   <div class="w3-container">
     <p class="w3-large"><b><i class="fa fa-asterisk fa-fw w3-margin-right w3-text-teal"></i>Галерея примеров</b>
     </p>
     <app-slider></app-slider>
     <hr>
   </div>
 </div><br>

Смотрим на результат

Реализуем сохранение рисунков для последующего редактирования.

Для того, что бы у нас были данные без использования базы данных мы создадим Mock.service и модель фейковых данных canvas.modelMock.ts, полный код на git , а структура класса без данных выглядит, так:

export class InMemoryCanvasModel { 
 get data() {
   return this._data;
 }
 _data = [
   {
     _id: ”61f0f9ad70c1c74fafc641f0”,
     name: "love",
     image: "love",
     canvas: ['для экономии места данные на git'],
}];
}

Получать наши фейковые данные будет наш canvas-post-mock.service.ts, которым мы заменим в компоненте main-canvas наш сервис CanvasPostMockService

import { InMemoryCanvasModel } from '../models/mock/canvas.modelMock';
 
@Injectable({
 providedIn: 'root'
})
export class CanvasPostMockService {
 private readonly list;
 private dataService: InMemoryCanvasModel;
 
 constructor() {
   this.dataService = new InMemoryCanvasModel();
   this.list = this.dataService.data ;
 } 
 getComponent(): Observable<CanvasPostInterface[]> {
   return of(this.list);
 } 
 getData(): Observable<CanvasPostInterface[]> {
   return of();
 } 
 getDataById(id: string): Observable<CanvasPostInterface> {
   return of();
 } 
 update(data: CanvasPostInterface): Observable<CanvasPostInterface> {
   return of();
 } 
 create(data: CanvasPostInterface): Observable<CanvasPostInterface> {
   return of();
 } 
 destroy(id: number): Observable<Response> {
   return of();
 }
}

Немного переделаем наш слайдер, чтобы была возможность передавать массив данных наших редактируемых изображений slide.component.html, что бы это выглядело так:

<div class="w3-content w3-display-container">
 <div *ngFor="let image of images">
   <img class="mySlides" src="{{image}}" style="width:400px">
 </div>
 <button class="w3-button w3-black w3-display-left" 
         (click)="plusDivs(-1)">&#10094;
  </button>
 <button class="w3-button w3-black w3-display-right" 
         (click)="plusDivs(1)">&#10095;
  </button>
</div>

Отредактируем slide.component.ts

//если данные не пришли значит берем эти данные 
images: CanvasPostInterface[] = [
    { _id: '', canvas: [], name: "", image: "assets/images/1.webp" },
    { _id: '', canvas: [], name: "", image: "assets/images/2.webp" },
    { _id: '', canvas: [], name: "", image: "assets/images/3.webp" },
    { _id: '', canvas: [], name: "", image: "assets/images/4.webp" }
  ];
  
@Input() imageInput: any;
……./*Если пришли данные то перезаписываем наш массив*/
 ngOnInit(): void {
   if (this.imageInput) {
     this.images = this.imageInput.map((data: any) => data.image)
   }
 }

Добавим в слайдер, галерею наших рисунков, для которых будет возможность редактирования main-canvas.component.html

<!-- images slider-->
<div class="w3-container w3-card w3-white w3-margin-bottom">
    <h2 class="w3-text-grey w3-padding-16">
      <i class="fa fa-paint-brush fa-fw w3-margin-right w3-xxlarge 
        w3-text-teal"></i>
      Поле рисования
    </h2>
   <div class="w3-container">
     //используем pip async
     <app-slider [imageInput]="canvasImg | async"></app-slider>
      
     <hr>
   </div>
 </div>
<!-- End images slider-->

Редактируем main.canvas.component.ts в дальнейшем сюда мы будем принимать наши рисунки из базы, а пока будем брать из фейковой модели:

//main.canvas.component.ts
canvasImg:  Observable<CanvasPostInterface[]>|undefined;
 constructor(private canvasPostService: CanvasPostService) { }
 ngOnInit(): void {
   this.canvasImg = this.canvasPostService.getComponent();
}

центрируем рисунок в slider, для этого добавим в slider.component.css немного стилей:

.mySlides {
 display:none;
} 
img.center-block {
 margin-left: auto;
 margin-right: auto;
}

Вот, что у нас должно получится:

Реализуем новую возможность рисования.

Как то не очень удобно рисовать кликами, по этому давайте добавим возможность закрашивать пиксели проводя над ними курсором, для того, чтобы получалось красиво эта функция будет доступна только с зажатой левой клавишей мыши. Для этого мы будем отслеживать событие mousemove, а закрашивать только если event.which равно единице, то есть нажата левая клавиша. А что бы не обрамлять код в условие, мы будем использовать отрицательную логику, а именно если клавиша не нажата, то ничего не делаем. 

//если клавиша не зажата выходим
if (event.which !== 1) {
  return;
}

Вот, что у нас получится.

this.rendererRef = this.renderer.listen(this.canvasRef.nativeElement,
                                        'mousemove', (event) => { 
       //если клавиша не зажата выходим
     if (event.which !== 1) {
       return;
     }

     let cX = event.offsetX;
     let cY = event.offsetY;
     const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
     const offsetTop = this.canvasRef.nativeElement.offsetTop;
     this.canvasX = cX - offsetLeft;
     this.canvasY = cY - offsetTop;

     this.matr.data.map(data => {
       if (cX >= data.x && cX < data.x + this.numberOf && cY >= data.y 
           && cY < data.y + this.numberOf) {
         this.ctx.fillStyle = this.colorRect;
         this.ctx.fillRect(data.x, data.y, this.numberOf, this.numberOf);
         data.color = this.colorRect;
       }
     })
     this.canvasRef.nativeElement.getBoundingClientRect()
     localStorage.setItem('matr', JSON.stringify(this.matr));
     const data = this.canvas?.toDataURL();
   });

Теперь у нас в двух местах появился дублирующий  код, поэтому создадим дополнительный метод eventOfset(event: MouseEvent) и вынесем этот код туда.

eventOfset(event: MouseEvent) {
   let cX = event.offsetX;
   let cY = event.offsetY;
   
   const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
   const offsetTop = this.canvasRef.nativeElement.offsetTop;
   this.canvasX = cX - offsetLeft;
   this.canvasY = cY - offsetTop;

   this.matr.data.map(data => {
     if (cX >= data.x && cX < data.x + this.numberOf && cY >= 
     data.y && cY < data.y + this.numberOf) {
       this.ctx.fillStyle = this.colorRect;
       this.ctx.fillRect(data.x, data.y, this.numberOf, this.numberOf);
       data.color = this.colorRect;
     }
   })
   this.canvasRef.nativeElement.getBoundingClientRect();
   localStorage.setItem('matr', JSON.stringify(this.matr));
   const data = this.canvas?.toDataURL();
 }

Соответственно внесем изменения в ngAfterViewInit():

  ngAfterViewInit() {
   ....
    this.rendererRef = this.renderer.listen(this.canvasRef.nativeElement, 'click', (event: MouseEvent) => {
     //добавили новый метод
      this.eventOfset(event)
    });
    this.rendererRef = this.renderer.listen(this.canvasRef.nativeElement, 'mousemove', (event) => {
      if (event.which !== 1) {
        return;
      }
       //добавили новый метод
      this.eventOfset(event);
    });

  }

Поскольку у нас несколько слайдеров, внесем некоторые дополнения которые помогут реагировать на событие только если это необходимо, ориентироваться будем на наличие данных  создаем в slider.component.ts переменную thereIsData = false, если данные пришли, то меняем в ngOnInit() ее значение на true. Сразу же создадим метод transferToСanvas() который будет отвечать за передачу нашего рисунка main-canvas, а после на наш холст.

Для этого в main-canvas.component.html редактируем строку внедрения в app-slider  и добавлем (pagesEventSlider)="dataSlider($event)", после чего это выглядит, так:

<app-slider (pagesEventSlider)="dataSlider($event)" 
  [imageInput]="canvasImg | async">
</app-slider>

 main-canvas.component.ts создаем метод dataSlider($event: {}) {   //TODO }

в slider.component.ts добавляем

@Output() pagesEventSlider: EventEmitter<{}> = new EventEmitter<{}>();

и соответственно редактируем метод transferToCanvas:

transferToCanvas(canvas: any) {
   if (!this.thereIsData) {     
   return;
   }
   this.pagesEventSlider.emit(canvas);
 }

Теперь по клику наши данные приходят в родительский для слайдера и холста компонент main-canvas.

Далее в canvas.component.ts создаем метод createTtransferToDataCanvas.

/* Метод воссоздания рисуноков из базы */
  createTtransferToDataCanvas(data: any) {
    this.cleardraw()
    this.matr = JSON.parse(data);
    const matr = JSON.parse(data);
    this.canvas.width = matr.innerWidth;
    this.canvas.height = matr.innerHeight;
    this.innerWidth = matr.innerWidth;
    this.innerHeight = matr.innerHeight;
    JSON.parse(data).data.map((data: any) => {
      this.ctx.fillStyle = data.color;
      this.ctx.fillRect(data.x, data.y, this.matr.numberOf,
                        this.matr.numberOf);
    })
  }

 В этом же компоненте добавляем:

  @Input() transferToDataCanvas: EventEmitter<{}>;
/*Добавляем подписку на наш transferToDataCanvas: EventEmitter 
в ngOnInit()*/

ngOnInit(): void {
    this.uploadSuccess.subscribe(data => {
      this.onResize(data);
    });  
    this.transferToDataCanvas.subscribe(data => {
      this.createTtransferToDataCanvas(data);
    });
  }

Переходим в main-canvas.component.html и редактируем:

<app-canvas 
  [transferToDataCanvas]="transferToDataCanvas"  
  [uploadSuccess]="uploadSuccess" 
  (pagesEventSave)="getSaveCanvas($event)"
></app-canvas>

Переходим в main-canvas.component.ts создаем эмиттер:

import { Component, EventEmitter, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { CanvasPostInterface } from 'src/app/shared/interfaces/canvas-post.interface';
import { CanvasPostMockService as CanvasPostService } from 'src/app/shared/services/canvas-post-mock.service';
//import { CanvasPostService   } from 'src/app/shared/services/canvas-post.service';

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

  uploadSuccess: EventEmitter<any> = new EventEmitter();
  canvasImgSuccess: EventEmitter<any> = new EventEmitter();
  //создаем эмиттер transferToDataCanvas
  transferToDataCanvas: EventEmitter<any> = new EventEmitter();
  canvasImg: Observable<CanvasPostInterface[]> | undefined;
  
  constructor(private canvasPostService: CanvasPostService) { }

  ngOnInit(): void {
    this.canvasImg = this.canvasPostService.getComponent();
    this.canvasPostService.getComponent().subscribe(respons => {
      console.log(respons[0].image)
    });   
  }

  /* Передаем данные из формы в канвас*/
  getDataPanel($event: FormGroup) {
    const data = {
      widthRect: Number($event.value.widthCanvas),
      heightRect: Number($event.value.heightCanvas),
      numberOf: Number($event.value.hwPixel),
      borderRow: $event.value.meshThickness,
      numberRow: Number($event.value.hwPixel) + Number($event.value.meshThickness),
      innerWidth: Number($event.value.heightCanvas) * (Number($event.value.hwPixel) + Number($event.value.meshThickness)),
      innerHeight: Number($event.value.widthCanvas) * (Number($event.value.hwPixel) + Number($event.value.meshThickness)),
      colorfillStyle: $event.value.colorFone
    }
    this.uploadSuccess.emit(data);//передали
  }  

  /* Принимает данные из слайдера и перадет в канвас*/
  dataSlider(data: {}) {
    console.log(data)
    this.transferToDataCanvas.emit(data);//передали
  }

  /* Принимает данные из панели и перадет в канвас*/
  pagesEventInMain(data: {}) {
    this.transferToDataCanvas.emit(data);//передали
  }
}

Проверяем, наш предварительно сохраненный рисунок по клику на рисунок, на панели Мои работы появляется новый холст. Отлично.

Создаем TAB элемент.

Сейчас наше приложение выглядит немного не эстетично.

Для придания более опрятного вида, реализуем Tab элемент куда перенесем Форму создания холста, Галерею примеров и Мои работы. Вот как это будет выглядеть:

Редактируем left-panel.component.html, что бы код в нем выглядел вот так:

<div class="w3-third" style="max-width: 600px;">

  <!--TAB-->
  <div class="w3-bar w3-black">
    <button class="w3-bar-item w3-button" (click)="openTab('one')">
      Форма создания холста
    </button>
    <button class="w3-bar-item w3-button" (click)="openTab('two')">
      Мои работы
    </button>
    <button class="w3-bar-item w3-button" (click)="openTab('three')">
      Галерея примеров
    </button>
  </div>

  <div id="one" class="tab" >
    <div class="w3-white w3-text-grey w3-card-4">
      <div class="w3-container">
        <p class="w3-large"><b><i class="fa fa-asterisk fa-fw w3-margin-right w3-text-teal"></i>Форма создания
            холста</b>
        </p>
        <div class="w3-light-grey w3-round-xlarge w3-small">
          <app-form (pagesEvent)="getData($event)"></app-form>
        </div>
        <hr>
      </div>
    </div><br>
  </div>

  <div id="two" class="tab" style="display:none">
    <div class="w3-white w3-text-grey w3-card-4">
      <div class="w3-container">
        <p class="w3-large"><b><i class="fa fa-asterisk fa-fw w3-margin-right w3-text-teal"></i>Мои работы</b>
        </p>
        <app-slider 
          (pagesEventSlider)="dataSlider($event)" 
          [imageInput]="canvasImg">
        </app-slider>
        <hr>
        <hr>
      </div>
    </div><br>
  </div>

  <div id="three" class="tab" style="display:none">
    <div class="w3-white w3-text-grey w3-card-4">
      <div class="w3-container">
        <div class="w3-white w3-text-grey w3-card-4">
          <div class="w3-container">
            <p class="w3-large"><b><i class="fa fa-asterisk fa-fw w3-margin-right w3-text-teal"></i>Галерея примеров</b>
            </p>
            <app-slider></app-slider>
            <hr>
          </div>
        </div><br>
      </div>
    </div><br>
  </div>

  <!--  -->
</div>

Добавляем метод управления вкладками openTab() в left-panel.component.ts

import { Component, ElementRef, EventEmitter, Input, OnInit, Output } 
from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { CanvasPostInterface } 
from 'src/app/shared/interfaces/canvas-post.interface';

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

  @Output() pagesEvent: EventEmitter<FormGroup> =
    new EventEmitter<FormGroup>();

  @Output() pagesEventLeftPanel: EventEmitter<{}> =
    new EventEmitter<{}>();

  @Input() imageInput: any;
  canvasImg: any;

  constructor(private elementRef: ElementRef) { }

  ngOnInit(): void {
    this.canvasImg = this.imageInput;//передали работы в слайдер
  }

  getData($event: FormGroup) {
    this.pagesEvent.emit($event);
  }

  dataSlider(data: {}) {
    this.pagesEventLeftPanel.emit(data);
   //передали в main
  }
/*Добавили метод управления вкладками*/
  openTab(tabId: string) {
    let i;
    let element = this.elementRef.nativeElement
      .getElementsByClassName("tab");
    for (i = 0; i < element.length; i++) {
      element[i].style.display = "none";
    }
    document.getElementById(tabId).style.display = "block";
  }

}

Вот в принципе и все. Хотя, давайте в завершение еще реализуем небольшое улучшение дающее нам возможность убирать сетку. Для этого добавим новый метод resizePixel() в canvas.component.ts

canvas.component.ts
import { AfterViewInit, Component, ElementRef, 
        EventEmitter, Input, OnInit, Output, Renderer2, ViewChild } 
from '@angular/core';
import { FormGroup } from '@angular/forms';

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

  @Output() pagesEventSave: EventEmitter<{}> =
    new EventEmitter<{}>();

  canvas: HTMLCanvasElement;
  innerWidth: number;
  innerHeight: number;
  rendererRef: any;
  numberRow: number;
  numberOf = 10; //размер пикселя
  borderRow = 1;
  widthRect = 50;
  heightRect = 50;
  x = 0;
  y = 0
  canvasX: number // X click cordinates
  canvasY: number // Y click cordinates
  colorRect = '#242323';//цвет пикселя рисовалки
  colorfillStyle = '#19a923';//цвет пикселя холста
  matr = { innerWidth: 0, innerHeight: 0, numberOf: 10, backgroundColor: '#19a923', data: [{ x: 0, y: 0, color: '' }] }
  name: string;
  private ctx: CanvasRenderingContext2D | null;
  @ViewChild('canvas') canvasRef: ElementRef;
  @Input() uploadSuccess: EventEmitter<FormGroup>;
  @Input() transferToDataCanvas: EventEmitter<{}>;

  constructor(private el: ElementRef,
    private renderer: Renderer2,
  ) {
    this.numberRow = this.numberOf + this.borderRow;
    this.innerWidth = this.heightRect * this.numberRow;
    this.innerHeight = this.widthRect * this.numberRow;
  }

  onResize(data: any) {
    this.name = data.name;
    this.innerWidth = data.innerWidth;
    this.innerHeight = data.innerHeight;
    this.widthRect = data.widthRect;
    this.heightRect = data.heightRect;
    this.numberOf = data.numberOf;
    this.borderRow = data.borderRow;
    this.numberRow = data.numberRow;
    this.canvas.width = this.innerWidth;
    this.canvas.height = this.innerHeight
    this.colorfillStyle = data.colorfillStyle;
    this.cleardraw()
    this.draw()
  }
  

  ngOnInit(): void {
    this.uploadSuccess.subscribe(data => {
      this.onResize(data);
    });
    this.transferToDataCanvas.subscribe(data => {
      this.createTtransferToDataCanvas(data);
    });
  }

  ngAfterViewInit() {
    this.canvas = this.canvasRef.nativeElement;
    this.canvas.width = this.innerWidth;
    this.canvas.height = this.innerHeight;
    this.cleardraw()
    this.draw();
    const data = this.canvas?.toDataURL();

    if (this.rendererRef != null) {
      this.rendererRef();
    }

    this.rendererRef = this.renderer
      .listen(this.canvasRef.nativeElement, 
              'click', (event: MouseEvent) => {
      this.eventOfset(event)
    });

    this.rendererRef = this.renderer
      .listen(this.canvasRef.nativeElement, 
              'mousemove', (event) => {
      if (event.which !== 1) {
        return;
      }
      this.eventOfset(event);
    });

  }

  draw(): void {
    this.matr.innerWidth = this.innerWidth;
    this.matr.innerHeight = this.innerHeight;
    this.matr.backgroundColor = this.colorfillStyle;
    this.matr.numberOf = this.numberOf;
    for (let i = 0; i < this.heightRect; i++) {
      for (let j = 0; j < this.widthRect; j++) {
        this.ctx.fillStyle = this.colorfillStyle;
        this.ctx.fillRect(j * this.numberRow, i * this.numberRow, 
                          this.numberOf, this.numberOf);
        this.matr.data.push({ x: j * this.numberRow, 
                             y: i * this.numberRow, 
                             color: this.colorfillStyle })
      }
    }

  }

  cleardraw(): void {
    this.ctx = this.canvas.getContext('2d');
    this.ctx.clearRect(0, 0, this.widthRect, this.heightRect);
    this.matr.data = [];
  }

  createFromLocalStorage(): void {
    const retrievedObject = localStorage.getItem('matr');
    this.matr = JSON.parse(retrievedObject);
    this.matr.data.map(data => {
      this.ctx.fillStyle = data.color;
      this.ctx.fillRect(data.x, data.y, this.matr.numberOf, 
                        this.matr.numberOf);
    })
  }

  /* Метод воссоздания рисуноков из базы */
  createTtransferToDataCanvas(data: any): void {
    this.cleardraw()
    this.matr = JSON.parse(data);
    const matr = JSON.parse(data);
    this.canvas.width = matr.innerWidth;
    this.canvas.height = matr.innerHeight;
    this.innerWidth = matr.innerWidth;
    this.innerHeight = matr.innerHeight;
    JSON.parse(data).data.map((data: any) => {
      this.ctx.fillStyle = data.color;
      this.ctx.fillRect(data.x, data.y, this.matr.numberOf, 
                        this.matr.numberOf);
    })
  }
  
  /* изменить размер пикселя */
  resizePixel(): void {
    this.matr.data.map(data => {
      this.ctx.fillStyle = data.color;
      this.ctx.fillRect(data.x, data.y, this.matr.numberOf + 1, 
                        this.matr.numberOf + 1);
    })
  }

  /* задаем цвет фону */
  clearPixel(): void {
    this.colorRect = this.matr.backgroundColor
  }
  /* сохраняем */
  save(): void {
    const data = this.canvas?.toDataURL();
    this.pagesEventSave.emit({ image: data, name: this.name, 
                              canvas: JSON.stringify(this.matr) });
  }

  /* функция рисования */
  eventOfset(event: MouseEvent): void {
    let cX = event.offsetX;
    let cY = event.offsetY;
    const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
    const offsetTop = this.canvasRef.nativeElement.offsetTop;
    this.canvasX = cX - offsetLeft;
    this.canvasY = cY - offsetTop;

    this.matr.data.map(data => {
      if (cX >= data.x && cX < data.x + this.numberOf && cY >= data.y 
          && cY < data.y + this.numberOf) {
        this.ctx.fillStyle = this.colorRect;
        this.ctx.fillRect(data.x, data.y, this.numberOf, this.numberOf);
        data.color = this.colorRect;
      }
    })
    this.canvasRef.nativeElement.getBoundingClientRect()
    localStorage.setItem('matr', JSON.stringify(this.matr));
    const data = this.canvas?.toDataURL();
  }
}

примет следующий вид:

Создадим кнопку по нажатию на которую будет убиратся сетка в canvas.component.html после чего его код будет выглядеть так:

<div class="w3-card-4">
  <div class="w3-container w3-center">
    <label for="colorWell"></label>
    <input list="" id="colorWell" type="color" name="colorRect" [(ngModel)]="colorRect">
    <button (click)="createFromLocalStorage()">Востановить рисунок</button>
    <button (click)="clearPixel()">Задать пикселю цвет фона</button>
    <button (click)="save()">Сохранить в галерею</button>
    <button (click)="resizePixel()">Убрать сетку</button>
  </div>
</div>
<div class="w3-card-4 layer">
  <div class="w3-container w3-center"  style="padding: 30px;">
    <canvas id="canvas" #canvas></canvas>
  </div>
</div>

Проверяем, нажимаем на кнопку Убрать сетку

сетка пропала, и наш слоник выглядит абсолютно по другому,

все работает.

Поскольку часто приходится работать сразу с несколькими проектами, давайте внесем изменения для запуска нашего приложения на порте 4300, для этого в angular.json внесем такие вот изменения:

    "defaultConfiguration": "production"
        },
        "serve": {
          "options": {
            "port": 4300
          },
          "builder": "@angular-devkit/build-angular:dev-server",

Примечание, проект запускается на 4300 порту. На сегодня все. Продолжение.

Демо, git