Angular — как получить доступ к элементу html при использовании ViewEncapsulation.Native

У меня возникла проблема, когда я получаю следующую ошибку при попытке доступа к элементу html по идентификатору. Я пытаюсь получить доступ к classList, когда пользователь нажимает кнопку, чтобы применить к элементу другой класс стиля. Список классов и элемент в целом всегда имеют значение null, если только я не переключу viewEncapsulation обратно на настройку эмуляции по умолчанию.

Сообщение об ошибке:

TypeError: Cannot read property 'classList' of null

Проблема: я пытаюсь сделать так, чтобы этот конкретный компонент не наследовал сторонний файл SCSS, который я импортировал в проект через файл angular.json. Когда я использую ViewEncapsulation.Emulated по умолчанию, компонент наследует глобальные стили SCSS, которые конфликтуют со стилями некоторых компонентов.

Файловая структура нормальная, SCSS и HTML кладу в соответствующие файлы, потом импортирую в компонент.

Я чувствую, что это должно быть общей темой для более крупных проектов. Если есть другой способ переключения стилей элементов в событиях, сообщите мне об этом.

Компонент .ts:

import { Component, ViewEncapsulation, OnInit } from '@angular/core';

@Component({
  encapsulation: ViewEncapsulation.Native,
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
  title = 'scssTesting';

  constructor() { }

  step: string = 'step1';

  step1: any;
  step2: any;
  step3: any;


  ngOnInit() {

  }

  next() {

    this.step1 = document.getElementById('step1');
    this.step2 = document.getElementById('step2');
    this.step3 = document.getElementById('step3');


    if (this.step === 'step1') {
      this.step = 'step2';
      this.step1.classList.remove("is-active");
      this.step1.classList.add("is-complete");
      this.step2.classList.add("is-active");

    } else if (this.step === 'step2') {
        ....
  }
}

Компонент .scss

.progress {
  position: relative;
  display: flex;
  .........
}

Компонент .html

<div class="container">
    <div class="progress">
        <div class="progress-track"></div>
        <div id="step1" class="progress-step">
            Step One
        </div>
        <div id="step2" class="progress-step">
            Step Two
        </div>
        <div id="step3" class="progress-step">
            Step Three
        </div>
    </div>
    <button (click)="next()">Next Step</button>
</div>

person thenolin    schedule 16.05.2019    source источник
comment
Вы пытались использовать ElementRef?   -  person Barbu Barbu    schedule 16.05.2019
comment
Не используйте документ. Есть много способов получить доступ к элементам представления. Пожалуйста, прочтите документацию для ViewChild и ViewChildren.   -  person Reactgular    schedule 16.05.2019
comment
Я думаю, что это тоже причина @cgTag, проверьте это и сообщите нам, пожалуйста   -  person Gaspar    schedule 17.05.2019
comment
Прежде всего, попробуйте использовать @ViewChild(), чтобы получить ElementRef, как предложено @Barbu Barbu и @cgTag. Во-вторых, если вы уже используете document, просто знайте, что небезопасно использовать document непосредственно в Angular. Вы должны использовать внедрение зависимостей и @Inject(DOCUMENT) (импортировать Inject из '@angular/core' и DOCUMENT из '@angular/common').   -  person Amit Beckenstein    schedule 17.05.2019


Ответы (2)


Самый простой способ получить доступ к HTML-элементу — использовать Angulars ElementRef. Подробнее об этом можно прочитать здесь. Пожалуйста, используйте это с осторожностью! Ниже приведен пример того, как использовать его в вашем случае:

В вашем app.component.html

<div #myelement>This is Pink</div>

В вашем app.component.ts

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  @ViewChild('myelement') el: ElementRef;

  ngAfterViewInit() {
    this.el.nativeElement.classList.add("myclass");
  }
}

В вашем app.component.css

.myclass {
    color: pink;
    font-weight: bold;
}

ИЗМЕНИТЬ:

Для чистого стиля, как уже упоминал @SnorreDan, было бы лучше использовать директиву Angulars [ngClass] . Вы можете посмотреть, как это сделать, на следующем примере:

В вашем app.component.html

<div [ngClass]="myColorClass">This may change color!</div>

<button (click)="makePink()">Pink</button>
<button (click)="makeGreen()">Green</button>

В вашем app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  myColorClass: string

  makePink() {
    this.myColorClass = 'pink';
  }

  makeGreen() {
    this.myColorClass = 'green';
  }
}

В вашем app.component.css

.pink {
    color: pink;
    font-weight: bold;
}

.green {
    color: green;
    font-weight: bold;
}

Надеюсь, это поможет!

person nullchimp    schedule 16.05.2019
comment
Потрясающий! В итоге это сработало, но в итоге я реализовал вышеуказанное решение с помощью ngClass из-за потенциальных опасностей, связанных с Element Ref. - person thenolin; 17.05.2019
comment
В любом случае для чистого стиля это лучшее решение. Мой подход был больше сосредоточен на доступе к элементу HTML. Я рад, что вы решили свою проблему. - person nullchimp; 18.05.2019
comment
Не возражаете ли вы добавить решение ngClass в свой ответ, чтобы люди могли видеть оба решения? Вот codepen с обновленным кодом: codepen.io/Skifficc/pen/qGPaqV. не работает в codepen, потому что технически это не проект Angular, но код работает в реальном проекте Angular. - person thenolin; 20.05.2019

Я думаю, вы задаете два разных вопроса:

<сильный>1. Как вы динамически добавляете и удаляете классы в Angular:

В Angular вы должны стараться избегать прямого использования документа. Хороший способ добавить класс на основе условия — использовать директиву ngClass, подробнее об этом можно прочитать здесь: https://angular.io/guide/template-syntax#ngclass

Другой способ динамического добавления класса — это захват элемента из шаблона с помощью ViewChild, а затем установка класса для элемента с помощью angular Renderer (вы найдете больше информации, если погуглите)

<сильный>2. Как сделать так, чтобы мой компонент не наследовал глобальный CSS из файла Angular.json

Я не могу придумать решение для этого. Использование стратегий инкапсуляции, таких как ViewEncapsulation.None и ViewEncapsulation.Emulated, не решит эту проблему, поскольку они влияют только на классы в этом компоненте, не распространяясь на другие компоненты. Это не влияет на глобальные классы, входящие в этот компонент.

person SnorreDan    schedule 16.05.2019
comment
Ваше предположение верно, это можно разделить на два вопроса. Я взгляну на документацию для первого решения, которое вы предоставили. Что касается второго вопроса в вашем ответе, когда я использую viewEncapsulation.Native, почему я не наследую какие-либо внешние или глобальные стили scss (css)? Какова основная цель использования Native по сравнению с настройкой Emulated по умолчанию? - person thenolin; 17.05.2019