Angular 7 [отключено] не обнаруживает изменения при изменении значений свойств вложенного объекта

Мне нужно написать общую функцию, которая будет включать/отключать кнопки на основе условий внутри JSON.

JSON:

    {
        PURCHASE_LIEN: {
          LABEL: 'PURCHASE LIEN',
          DISABLE: true,
          CONDITION: [
            {
              ReviewPurchaseDecisionStatus: true,
              PurchaseDecisionStatus: false
            },
            {
              ReviewPurchaseDecisionStatus: true,
              'ReviewPurchaseDecision.Status': 'NOT QUALIFIED'
            }
          ]
        },
        NOT_QUALIFIED: {
          LABEL: 'NOT QUALIFIED',
          DISABLE: true,
          CONDITION: [
            {
              ReviewPurchaseDecisionStatus: true,
              PurchaseDecisionStatus: false
            },
            {
              ReviewPurchaseDecisionStatus: true,
              'ReviewPurchaseDecision.Status': 'PURCHASED'
            }
          ]
        }
  }

В JSON у меня есть две кнопки «PURCHASE_LIEN» и «NOT_QUALIFIED». Обе функции имеют набор условий, основанных на условии, кнопка должна включаться/отключаться с помощью свойства "DISABLE".

ВАЛИДАЦИЯ.СЕРВИС.ТС

Приведенная ниже функция установит свойство DISABLE кнопки на основе условий для объекта (selectedRow).

public disableButton(buttonContainer: any, buttonID: string, selectedRow: any) {
    let status = true;
    for (let i = 0; i < buttonContainer[buttonID]['CONDITION'].length; i++) {
      const condition = buttonContainer[buttonID]['CONDITION'][i];
      for (const conditionName in condition) {
        if (condition[conditionName] !== selectedRow[condition]) {
          status = false;
        }
      }
      if (status) {
        buttonContainer[buttonID].DISABLE = false;
        break;
      } else {
        buttonContainer[buttonID].DISABLE = true;
      }

    }

    return buttonContainer;
  }

app.component.html

 <div class="col-12 col-sm-6 col-md-4 col-lg-2">
        <button class="btn btn-primary btn-semi-circle" (click)="showModal('Purchase')"
         [disabled]="disableButton(buttonGroup, 'PURCHASE_LIEN', selectedRowData)">Purchase
          Lien</button>
          <!-- [disabled]="PURCHASE_LIEN_DISABLE" -->
      </div>
      <div class="col-12 col-sm-6 col-md-4 col-lg-2">
        <button class="btn btn-danger btn-semi-circle" (click)="showModal('Not Qualified')"
         [disabled]="disableButton(buttonGroup, 'NOT_QUALIFIED', selectedRowData)">Not
          Qualified</button>
          <!-- [disabled]="NOT_QUALIFIED_DISABLE" -->
      </div>

app.component.ts

export class ReviewPurchaseDecisionComponent implements OnInit {
  public buttonGroup: any = {
    PURCHASE_LIEN: {
      LABEL: 'PURCHASE LIEN',
      DISABLE: true,
      CONDITION: [
        {
          ReviewPurchaseDecisionStatus: true,
          PurchaseDecisionStatus: false
        },
        {
          ReviewPurchaseDecisionStatus: true,
          'ReviewPurchaseDecision.Status': 'NOT QUALIFIED'
        }
      ]
    },
    NOT_QUALIFIED: {
      LABEL: 'NOT QUALIFIED',
      DISABLE: true,
      CONDITION: [
        {
          ReviewPurchaseDecisionStatus: true,
          PurchaseDecisionStatus: false
        },
        {
          ReviewPurchaseDecisionStatus: true,
          'ReviewPurchaseDecision.Status': 'PURCHASED'
        }
      ]
    }
  };

  constructor(
    public router: Router,
    public validation: ValidationService,
    private fb: FormBuilder,
    private http: HttpHelperService,
    private myMonitoringService: MyMonitoringService,
    private authentication: AuthenticationService,
    private sessionService: SessionService,
    public dialogService: DialogServiceService,
    private caseService: CaseService,
    private cookieService: CookieService
  ) {}

  disableButton(buttonContainer: any, buttonID: string, selectedRow: any) {
    this.buttonGroup = this.validation.disableButton(
      buttonContainer,
      buttonID,
      selectedRow
    );
    return this.buttonGroup[buttonID].DISABLE;
  }
}

Метод disableButton в службе проверки изменяет значение свойства DISABLE на true/false в зависимости от условий, но кнопка не активируется. Он не обнаруживает изменения


person Bear Nithi    schedule 11.01.2019    source источник
comment
Вы можете создать stackblitz?   -  person Abhishek    schedule 11.01.2019
comment
Журнал консоли до return buttonContainer;, чтобы увидеть логическое значение.   -  person Arcteezy    schedule 11.01.2019
comment
Я проверил объект с помощью консоли разработчика, свойство изменилось, но angular не обнаруживает изменений   -  person Bear Nithi    schedule 11.01.2019
comment
Возможный дубликат Как выполнять манипуляции с DOM в компонентах Angular?   -  person Randy Casburn    schedule 11.01.2019
comment
Я не спрашиваю о манипуляциях с DOM. Я просто хочу знать, как angular знает, что свойство объекта было изменено, поэтому angular обнаруживает его и обновляет свойство отключенной кнопки.   -  person Bear Nithi    schedule 11.01.2019
comment
@BearNithi Можете ли вы создать демо?   -  person Arcteezy    schedule 11.01.2019
comment
Для обнаружения асинхронных изменений можно использовать ChangeDetectorRef или ngZone. Проверьте этот ответ stackoverflow.com/a/48663599/8932763   -  person gaborp    schedule 11.01.2019


Ответы (2)


Обнаружение изменений в angular — огромная тема. Здесь у вас есть очень вложенный объект, где angular должен отслеживать изменения.

Для этого angular нет возможности рекурсивно проверять каждое поле вашего объекта и сравнивать его с предыдущим состоянием, чтобы определить, внесли ли вы какие-либо изменения. Этот шаг называется дайджестом и помечается как грязный. Является очень ресурсоемким, поэтому angular делает это в очень конкретном случае (список неполный только для демонстрации):

  • @Выход является триггером
  • @Input мутирует
  • событие браузера - это отправка (щелчок, наведение и т. д.)
  • тайм-аут
  • интервал
  • ....

здесь вы изменяете свой объект, вызывая функцию из атрибута [disable] html. Я подозреваю, что этот случай не распространяется на changeDetectionStrategy по умолчанию.

В любом случае команда angular не рекомендует манипулировать такими объектами. Рекомендуется использовать любой из этих двух подходов: - Избегайте изменения состояния, предпочитайте создавать новый объект и заменять предыдущий. Подобно этому угловому простому, нужно делать myPreviousObject !== myNewObject вместо:

if (
    myPreviousObject.prop1 !== myNewObject.prop1 ||
    myPreviousObject.prop2 !== myPreviousObject.prop2 ||
    ....
)
  • Используйте Observable с неизменяемым состоянием.

для моей демонстрации я использую второй подход, и здесь вы можете найти простую реализацию:

моя модель:

export interface disableState {
  PURCHASE_LIEN: {
    disable: boolean;
    disable$: BehaviorSubject<boolean>
  };

  NOT_QUALIFIED: {
    disable: boolean;
    disable$: BehaviorSubject<boolean>
  };
}

внутри моего компонента у меня есть такое свойство:

disableState: disableState = {
    NOT_QUALIFIED: { 
      disable: false, 
      disable$: new BehaviorSubject<boolean>(false),
      },
   PURCHASE_LIEN: { 
      disable: false, 
      disable$: new BehaviorSubject<boolean>(false),
      },
  }

и когда я хочу изменить это значение, я могу сделать это так:

/**
 * Call your service like :
 * this.validation.disableButton()
 */
this.disableState['NOT_QUALIFIED'].disable = true;
this.disableState['NOT_QUALIFIED'].disable$.next(true);

живое кодирование

Полный компонент:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent  {

  disableState: disableState = {
    NOT_QUALIFIED: { 
      disable: false, 
      disable$: new BehaviorSubject<boolean>(false),
      },
   PURCHASE_LIEN: { 
      disable: false, 
      disable$: new BehaviorSubject<boolean>(false),
      },
  }

  showModal(id: string) {
    console.log(`Open modal : ${id}`);
  }


  dummyPropertyChange() {
    /**
     * Call your service like :
     * this.validation.disableButton()
     */
    this.disableState['NOT_QUALIFIED'].disable = true;
    this.disableState['NOT_QUALIFIED'].disable$.next(true);
  }
}

Обратите внимание, что я переключил ChangeDetectionStrategy, чтобы попросить angular выполнять только грязную проверку путем простого сравнения объектов (вместо вложенного). Отныне, если я хочу обновить данные, я должен:

  • изменить все переменные данные (вместо только вложенного свойства)
  • используйте observable для выполнения изменений на временной шкале.
person Yanis-git    schedule 11.01.2019

Я решил эту проблему, создав директиву. Директива будет получать доступ к nativeElement кнопки, она включает/отключает кнопку на основе условия в JSON.

btn-disable.directive.ts

import {
  Directive,
  Renderer2,
  ElementRef,
  Input,
  OnChanges
} from '@angular/core';


@Directive({
  selector: '[appBtnDisable]'
})
export class BtnDisableDirective implements OnChanges {
  @Input() buttonContainer: any = {};
  @Input() buttonID = '';
  @Input() condition: any = {};

  constructor(public ele: ElementRef, public renderer: Renderer2) {
    if (this.buttonID) {
      this.disableButton(this.buttonContainer, this.buttonID, this.condition);
    }
  }

  ngOnChanges() {
    if (this.buttonID) {
      this.disableButton(this.buttonContainer, this.buttonID, this.condition);
    }
  }

  public disableButton(
    buttonContainer: any,
    buttonID: string,
    selectedRow: any
  ) {

    for (let i = 0; i < buttonContainer[buttonID]['CONDITION'].length; i++) {
      const condition = buttonContainer[buttonID]['CONDITION'][i];
      let status = true;
      for (const conditionName in condition) {
        if (
          this.convertNulltoUndefined(condition[conditionName]) !== this.evaluate(conditionName, selectedRow)
        ) {
          status = false;
        }
      }

      if (status) {
        this.ele.nativeElement.disabled = false;
        break;
      } else {
        this.ele.nativeElement.disabled = true;
      }
    }
  }



  evaluate(data: string, selectedRow: any): any {
    if (data.split('.').length > 1) {
      const value = 'selectedRow.' + data;
      try {
        return eval(value);
      } catch (error) {
        return undefined;
      }
    } else {
      return selectedRow[data];
    }
  }

  convertNulltoUndefined(data) {
    return (data === null) ? undefined : data;
  }
}

app.component.html

  <button class="btn btn-primary btn-semi-circle" (click)="showModal('Purchase')"
            appBtnDisable [condition]="selectedRowData" [buttonContainer]="buttonGroup" [buttonID]="'PURCHASE_LIEN'">Purchase
              Lien</button>
person Bear Nithi    schedule 11.01.2019
comment
почему мы не можем добиться этого без нашей директивы - person M.S.Udhaya Raj; 25.01.2019